package gr.uom.java.xmi.diff;

import gr.uom.java.xmi.UMLAnonymousClass;
import gr.uom.java.xmi.UMLAttribute;
import gr.uom.java.xmi.UMLClass;
import gr.uom.java.xmi.UMLClassMatcher;
import gr.uom.java.xmi.UMLGeneralization;
import gr.uom.java.xmi.UMLOperation;
import gr.uom.java.xmi.UMLParameter;
import gr.uom.java.xmi.UMLRealization;
import gr.uom.java.xmi.UMLType;
import gr.uom.java.xmi.decomposition.AbstractCodeMapping;
import gr.uom.java.xmi.decomposition.AbstractExpression;
import gr.uom.java.xmi.decomposition.CompositeStatementObject;
import gr.uom.java.xmi.decomposition.CompositeStatementObjectMapping;
import gr.uom.java.xmi.decomposition.LeafMapping;
import gr.uom.java.xmi.decomposition.OperationInvocation;
import gr.uom.java.xmi.decomposition.StatementObject;
import gr.uom.java.xmi.decomposition.UMLOperationBodyMapper;
import gr.uom.java.xmi.decomposition.UMLOperationBodyMapperComparator;
import gr.uom.java.xmi.decomposition.VariableDeclaration;
import gr.uom.java.xmi.decomposition.replacement.MergeVariableReplacement;
import gr.uom.java.xmi.decomposition.replacement.Replacement;
import gr.uom.java.xmi.decomposition.replacement.Replacement.ReplacementType;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.refactoringminer.api.Refactoring;
import org.refactoringminer.api.RefactoringMinerTimedOutException;
import org.refactoringminer.api.RefactoringType;
import org.refactoringminer.util.PrefixSuffixUtils;

public class UMLModelDiff {
   private static final int MAXIMUM_NUMBER_OF_COMPARED_METHODS = 100;
   private List<UMLClass> addedClasses;
   private List<UMLClass> removedClasses;
   
   private List<UMLGeneralization> addedGeneralizations;
   private List<UMLGeneralization> removedGeneralizations;
   private List<UMLGeneralizationDiff> generalizationDiffList;
   private List<UMLRealization> addedRealizations;
   private List<UMLRealization> removedRealizations;
   private List<UMLRealizationDiff> realizationDiffList;
   
   private List<UMLClassDiff> commonClassDiffList;
   private List<UMLClassMoveDiff> classMoveDiffList;
   private List<UMLClassMoveDiff> innerClassMoveDiffList;
   private List<UMLClassRenameDiff> classRenameDiffList;
   private List<Refactoring> refactorings;
   private Set<String> deletedFolderPaths;
   
   public UMLModelDiff() {
      this.addedClasses = new ArrayList<UMLClass>();
      this.removedClasses = new ArrayList<UMLClass>();
      this.addedGeneralizations = new ArrayList<UMLGeneralization>();
      this.removedGeneralizations = new ArrayList<UMLGeneralization>();
      this.generalizationDiffList = new ArrayList<UMLGeneralizationDiff>();
      this.realizationDiffList = new ArrayList<UMLRealizationDiff>();
      this.addedRealizations = new ArrayList<UMLRealization>();
      this.removedRealizations = new ArrayList<UMLRealization>();
      this.commonClassDiffList = new ArrayList<UMLClassDiff>();
      this.classMoveDiffList = new ArrayList<UMLClassMoveDiff>();
      this.innerClassMoveDiffList = new ArrayList<UMLClassMoveDiff>();
      this.classRenameDiffList = new ArrayList<UMLClassRenameDiff>();
      this.refactorings = new ArrayList<Refactoring>();
      this.deletedFolderPaths = new LinkedHashSet<String>();
   }

   public void reportAddedClass(UMLClass umlClass) {
	   if(!addedClasses.contains(umlClass))
		   this.addedClasses.add(umlClass);
   }

   public void reportRemovedClass(UMLClass umlClass) {
	   if(!removedClasses.contains(umlClass))
		   this.removedClasses.add(umlClass);
   }

   public void reportAddedGeneralization(UMLGeneralization umlGeneralization) {
      this.addedGeneralizations.add(umlGeneralization);
   }

   public void reportRemovedGeneralization(UMLGeneralization umlGeneralization) {
      this.removedGeneralizations.add(umlGeneralization);
   }

   public void reportAddedRealization(UMLRealization umlRealization) {
      this.addedRealizations.add(umlRealization);
   }

   public void reportRemovedRealization(UMLRealization umlRealization) {
      this.removedRealizations.add(umlRealization);
   }

   public void addUMLClassDiff(UMLClassDiff classDiff) {
      this.commonClassDiffList.add(classDiff);
   }

   public boolean commonlyImplementedOperations(UMLOperation operation1, UMLOperation operation2, UMLClassBaseDiff classDiff2) {
	   UMLClassBaseDiff classDiff1 = getUMLClassDiff(operation1.getClassName());
	   if(classDiff1 != null) {
		   Set<UMLType> commonInterfaces = classDiff1.nextClassCommonInterfaces(classDiff2);
		   for(UMLType commonInterface : commonInterfaces) {
			   UMLClassBaseDiff interfaceDiff = getUMLClassDiff(commonInterface);
			   if(interfaceDiff != null &&
					   interfaceDiff.containsOperationWithTheSameSignatureInOriginalClass(operation1) &&
					   interfaceDiff.containsOperationWithTheSameSignatureInNextClass(operation2)) {
				   return true;
			   }
		   }
	   }
	   return false;
   }

   private UMLClassBaseDiff getUMLClassDiff(String className) {
      for(UMLClassDiff classDiff : commonClassDiffList) {
         if(classDiff.matches(className))
            return classDiff;
      }
      for(UMLClassMoveDiff classDiff : classMoveDiffList) {
         if(classDiff.matches(className))
            return classDiff;
      }
      for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
         if(classDiff.matches(className))
            return classDiff;
      }
      for(UMLClassRenameDiff classDiff : classRenameDiffList) {
         if(classDiff.matches(className))
            return classDiff;
      }
      return null;
   }

   private UMLClassBaseDiff getUMLClassDiff(UMLType type) {
      for(UMLClassDiff classDiff : commonClassDiffList) {
         if(classDiff.matches(type))
            return classDiff;
      }
      for(UMLClassMoveDiff classDiff : classMoveDiffList) {
         if(classDiff.matches(type))
            return classDiff;
      }
      for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
         if(classDiff.matches(type))
            return classDiff;
      }
      for(UMLClassRenameDiff classDiff : classRenameDiffList) {
         if(classDiff.matches(type))
            return classDiff;
      }
      return null;
   }

   private UMLClassBaseDiff getUMLClassDiffWithAttribute(Replacement pattern) {
      for(UMLClassDiff classDiff : commonClassDiffList) {
         if(classDiff.findAttributeInOriginalClass(pattern.getBefore()) != null &&
        		 classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
            return classDiff;
      }
      for(UMLClassMoveDiff classDiff : classMoveDiffList) {
         if(classDiff.findAttributeInOriginalClass(pattern.getBefore()) != null &&
        		 classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
            return classDiff;
      }
      for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
         if(classDiff.findAttributeInOriginalClass(pattern.getBefore()) != null &&
        		 classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
            return classDiff;
      }
      for(UMLClassRenameDiff classDiff : classRenameDiffList) {
         if(classDiff.findAttributeInOriginalClass(pattern.getBefore()) != null &&
        		 classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
            return classDiff;
      }
      return null;
   }

   private List<UMLClassBaseDiff> getUMLClassDiffWithExistingAttributeAfter(Replacement pattern) {
	   List<UMLClassBaseDiff> classDiffs = new ArrayList<UMLClassBaseDiff>();
	   for(UMLClassDiff classDiff : commonClassDiffList) {
		   if(classDiff.findAttributeInOriginalClass(pattern.getAfter()) != null &&
				   classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
			   classDiffs.add(classDiff);
	   }
	   for(UMLClassMoveDiff classDiff : classMoveDiffList) {
		   if(classDiff.findAttributeInOriginalClass(pattern.getAfter()) != null &&
				   classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
			   classDiffs.add(classDiff);
	   }
	   for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
		   if(classDiff.findAttributeInOriginalClass(pattern.getAfter()) != null &&
				   classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
			   classDiffs.add(classDiff);
	   }
	   for(UMLClassRenameDiff classDiff : classRenameDiffList) {
		   if(classDiff.findAttributeInOriginalClass(pattern.getAfter()) != null &&
				   classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
			   classDiffs.add(classDiff);
	   }
	   return classDiffs;
   }

   private List<UMLClassBaseDiff> getUMLClassDiffWithNewAttributeAfter(Replacement pattern) {
	   List<UMLClassBaseDiff> classDiffs = new ArrayList<UMLClassBaseDiff>();
	   for(UMLClassDiff classDiff : commonClassDiffList) {
		   if(classDiff.findAttributeInOriginalClass(pattern.getAfter()) == null &&
				   classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
			   classDiffs.add(classDiff);
	   }
	   for(UMLClassMoveDiff classDiff : classMoveDiffList) {
		   if(classDiff.findAttributeInOriginalClass(pattern.getAfter()) == null &&
				   classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
			   classDiffs.add(classDiff);
	   }
	   for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
		   if(classDiff.findAttributeInOriginalClass(pattern.getAfter()) == null &&
				   classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
			   classDiffs.add(classDiff);
	   }
	   for(UMLClassRenameDiff classDiff : classRenameDiffList) {
		   if(classDiff.findAttributeInOriginalClass(pattern.getAfter()) == null &&
				   classDiff.findAttributeInNextClass(pattern.getAfter()) != null)
			   classDiffs.add(classDiff);
	   }
	   return classDiffs;
   }

   public boolean isSubclassOf(String subclass, String finalSuperclass) {
	   return isSubclassOf(subclass, finalSuperclass, new LinkedHashSet<String>());
   }

   private boolean isSubclassOf(String subclass, String finalSuperclass, Set<String> visitedClasses) {
	   if(visitedClasses.contains(subclass)) {
		   return false;
	   }
	   else {
		   visitedClasses.add(subclass);
	   }
	   UMLClassBaseDiff subclassDiff = getUMLClassDiff(subclass);
	   if(subclassDiff == null) {
		   subclassDiff = getUMLClassDiff(UMLType.extractTypeObject(subclass));
	   }
	   if(subclassDiff != null) {
		   UMLType superclass = subclassDiff.getSuperclass();
		   if(superclass != null) {
			   if(checkInheritanceRelationship(superclass, finalSuperclass, visitedClasses)) {
				   return true;
			   }
		   }
		   else if(subclassDiff.getOldSuperclass() != null && subclassDiff.getNewSuperclass() != null &&
				   !subclassDiff.getOldSuperclass().equals(subclassDiff.getNewSuperclass()) && looksLikeAddedClass(subclassDiff.getNewSuperclass()) != null) {
			   UMLClass addedClass = looksLikeAddedClass(subclassDiff.getNewSuperclass());
			   if(addedClass.getSuperclass() != null) {
				   return checkInheritanceRelationship(addedClass.getSuperclass(), finalSuperclass, visitedClasses);
			   }
		   }
		   else if(subclassDiff.getOldSuperclass() == null && subclassDiff.getNewSuperclass() != null && looksLikeAddedClass(subclassDiff.getNewSuperclass()) != null) {
			   UMLClass addedClass = looksLikeAddedClass(subclassDiff.getNewSuperclass());
			   return checkInheritanceRelationship(UMLType.extractTypeObject(addedClass.getName()), finalSuperclass, visitedClasses);
		   }
		   for(UMLType implementedInterface : subclassDiff.getAddedImplementedInterfaces()) {
			   if(checkInheritanceRelationship(implementedInterface, finalSuperclass, visitedClasses)) {
				   return true;
			   }
		   }
		   for(UMLType implementedInterface : subclassDiff.getNextClass().getImplementedInterfaces()) {
			   if(checkInheritanceRelationship(implementedInterface, finalSuperclass, visitedClasses)) {
				   return true;
			   }
		   }
	   }
	   UMLClass addedClass = getAddedClass(subclass);
	   if(addedClass == null) {
		   addedClass = looksLikeAddedClass(UMLType.extractTypeObject(subclass));
	   }
	   if(addedClass != null) {
		   UMLType superclass = addedClass.getSuperclass();
		   if(superclass != null) {
			   return checkInheritanceRelationship(superclass, finalSuperclass, visitedClasses);
		   }
		   for(UMLType implementedInterface : addedClass.getImplementedInterfaces()) {
			   if(checkInheritanceRelationship(implementedInterface, finalSuperclass, visitedClasses)) {
				   return true;
			   }
		   }
	   }
	   UMLClass removedClass = getRemovedClass(subclass);
	   if(removedClass == null) {
		   removedClass = looksLikeRemovedClass(UMLType.extractTypeObject(subclass));
	   }
	   if(removedClass != null) {
		   UMLType superclass = removedClass.getSuperclass();
		   if(superclass != null) {
			   return checkInheritanceRelationship(superclass, finalSuperclass, visitedClasses);
		   }
		   for(UMLType implementedInterface : removedClass.getImplementedInterfaces()) {
			   if(checkInheritanceRelationship(implementedInterface, finalSuperclass, visitedClasses)) {
				   return true;
			   }
		   }
	   }
	   return false;
   }

   private boolean checkInheritanceRelationship(UMLType superclass, String finalSuperclass, Set<String> visitedClasses) {
	   if(looksLikeSameType(superclass.getClassType(), finalSuperclass))
		   return true;
	   else
		   return isSubclassOf(superclass.getClassType(), finalSuperclass, visitedClasses);
   }

   private UMLClass looksLikeAddedClass(UMLType type) {
	   for(UMLClass umlClass : addedClasses) {
	         if(umlClass.getName().endsWith("." + type.getClassType())) {
	        	 return umlClass;
	         }
	   }
	   return null;
   }

   private UMLClass looksLikeRemovedClass(UMLType type) {
	   for(UMLClass umlClass : removedClasses) {
	         if(umlClass.getName().endsWith("." + type.getClassType())) {
	        	 return umlClass;
	         }
	   }
	   return null;
   }

   public UMLClass getAddedClass(String className) {
      for(UMLClass umlClass : addedClasses) {
         if(umlClass.getName().equals(className))
            return umlClass;
      }
      return null;
   }

   public UMLClass getRemovedClass(String className) {
      for(UMLClass umlClass : removedClasses) {
         if(umlClass.getName().equals(className))
            return umlClass;
      }
      return null;
   }

   private String isRenamedClass(UMLClass umlClass) {
      for(UMLClassRenameDiff renameDiff : classRenameDiffList) {
         if(renameDiff.getOriginalClass().equals(umlClass))
            return renameDiff.getRenamedClass().getName();
      }
      return null;
   }

   private String isMovedClass(UMLClass umlClass) {
      for(UMLClassMoveDiff moveDiff : classMoveDiffList) {
         if(moveDiff.getOriginalClass().equals(umlClass))
            return moveDiff.getMovedClass().getName();
      }
      return null;
   }

   public void checkForGeneralizationChanges() {
      for(Iterator<UMLGeneralization> removedGeneralizationIterator = removedGeneralizations.iterator(); removedGeneralizationIterator.hasNext();) {
         UMLGeneralization removedGeneralization = removedGeneralizationIterator.next();
         for(Iterator<UMLGeneralization> addedGeneralizationIterator = addedGeneralizations.iterator(); addedGeneralizationIterator.hasNext();) {
            UMLGeneralization addedGeneralization = addedGeneralizationIterator.next();
            String renamedChild = isRenamedClass(removedGeneralization.getChild());
            String movedChild = isMovedClass(removedGeneralization.getChild());
            if(removedGeneralization.getChild().equals(addedGeneralization.getChild())) {
               UMLGeneralizationDiff generalizationDiff = new UMLGeneralizationDiff(removedGeneralization, addedGeneralization);
               addedGeneralizationIterator.remove();
               removedGeneralizationIterator.remove();
               generalizationDiffList.add(generalizationDiff);
               break;
            }
            if( (renamedChild != null && renamedChild.equals(addedGeneralization.getChild().getName())) ||
                  (movedChild != null && movedChild.equals(addedGeneralization.getChild().getName()))) {
               UMLGeneralizationDiff generalizationDiff = new UMLGeneralizationDiff(removedGeneralization, addedGeneralization);
               addedGeneralizationIterator.remove();
               removedGeneralizationIterator.remove();
               generalizationDiffList.add(generalizationDiff);
               break;
            }
         }
      }
   }

   public void checkForRealizationChanges() {
      for(Iterator<UMLRealization> removedRealizationIterator = removedRealizations.iterator(); removedRealizationIterator.hasNext();) {
         UMLRealization removedRealization = removedRealizationIterator.next();
         for(Iterator<UMLRealization> addedRealizationIterator = addedRealizations.iterator(); addedRealizationIterator.hasNext();) {
            UMLRealization addedRealization = addedRealizationIterator.next();
            String renamedChild = isRenamedClass(removedRealization.getClient());
            String movedChild = isMovedClass(removedRealization.getClient());
            //String renamedParent = isRenamedClass(removedRealization.getSupplier());
            //String movedParent = isMovedClass(removedRealization.getSupplier());
            if( (renamedChild != null && renamedChild.equals(addedRealization.getClient().getName())) ||
                  (movedChild != null && movedChild.equals(addedRealization.getClient().getName()))) {
               UMLRealizationDiff realizationDiff = new UMLRealizationDiff(removedRealization, addedRealization);
               addedRealizationIterator.remove();
               removedRealizationIterator.remove();
               realizationDiffList.add(realizationDiff);
               break;
            }
         }
      }
   }

   public void checkForMovedClasses(Map<String, String> renamedFileHints, Set<String> repositoryDirectories, UMLClassMatcher matcher) throws RefactoringMinerTimedOutException {
	   for(Iterator<UMLClass> removedClassIterator = removedClasses.iterator(); removedClassIterator.hasNext();) {
		   UMLClass removedClass = removedClassIterator.next();
		   TreeSet<UMLClassMoveDiff> diffSet = new TreeSet<UMLClassMoveDiff>(new ClassMoveComparator());
		   for(Iterator<UMLClass> addedClassIterator = addedClasses.iterator(); addedClassIterator.hasNext();) {
			   UMLClass addedClass = addedClassIterator.next();
			   String removedClassSourceFile = removedClass.getSourceFile();
			   String renamedFile =  renamedFileHints.get(removedClassSourceFile);
			   String removedClassSourceFolder = "";
			   if(removedClassSourceFile.contains("/")) {
				   removedClassSourceFolder = removedClassSourceFile.substring(0, removedClassSourceFile.lastIndexOf("/"));
			   }
			   if(!repositoryDirectories.contains(removedClassSourceFolder)) {
				   deletedFolderPaths.add(removedClassSourceFolder);
				   //add deleted sub-directories
				   String subDirectory = new String(removedClassSourceFolder);
				   while(subDirectory.contains("/")) {
					   subDirectory = subDirectory.substring(0, subDirectory.lastIndexOf("/"));
					   if(!repositoryDirectories.contains(subDirectory)) {
						   deletedFolderPaths.add(subDirectory);
					   }
				   }
			   }
			   if(matcher.match(removedClass, addedClass, renamedFile)) {
				   if(!conflictingMoveOfTopLevelClass(removedClass, addedClass)) {
					   UMLClassMoveDiff classMoveDiff = new UMLClassMoveDiff(removedClass, addedClass, this);
					   diffSet.add(classMoveDiff);
				   }
			   }
		   }
		   if(!diffSet.isEmpty()) {
			   UMLClassMoveDiff minClassMoveDiff = diffSet.first();
			   minClassMoveDiff.process();
			   classMoveDiffList.add(minClassMoveDiff);
			   addedClasses.remove(minClassMoveDiff.getMovedClass());
			   removedClassIterator.remove();
		   }
	   }

	   List<UMLClassMoveDiff> allClassMoves = new ArrayList<UMLClassMoveDiff>(this.classMoveDiffList);
	   Collections.sort(allClassMoves);

	   for(int i=0; i<allClassMoves.size(); i++) {
		   UMLClassMoveDiff classMoveI = allClassMoves.get(i);
		   for(int j=i+1; j<allClassMoves.size(); j++) {
			   UMLClassMoveDiff classMoveJ = allClassMoves.get(j);
			   if(classMoveI.isInnerClassMove(classMoveJ)) {
				   innerClassMoveDiffList.add(classMoveJ);
			   }
		   }
	   }
	   this.classMoveDiffList.removeAll(innerClassMoveDiffList);
   }

   private boolean conflictingMoveOfTopLevelClass(UMLClass removedClass, UMLClass addedClass) {
	   if(!removedClass.isTopLevel() && !addedClass.isTopLevel()) {
		   //check if classMoveDiffList contains already a move for the outer class to a different target
		   for(UMLClassMoveDiff diff : classMoveDiffList) {
			   if((diff.getOriginalClass().getName().startsWith(removedClass.getPackageName()) &&
					   !diff.getMovedClass().getName().startsWith(addedClass.getPackageName())) ||
					   (!diff.getOriginalClass().getName().startsWith(removedClass.getPackageName()) &&
						diff.getMovedClass().getName().startsWith(addedClass.getPackageName()))) {
				   return true;
			   }
		   }
	   }
	   return false;
   }

   public void checkForRenamedClasses(Map<String, String> renamedFileHints, UMLClassMatcher matcher) throws RefactoringMinerTimedOutException {
      for(Iterator<UMLClass> removedClassIterator = removedClasses.iterator(); removedClassIterator.hasNext();) {
         UMLClass removedClass = removedClassIterator.next();
         TreeSet<UMLClassRenameDiff> diffSet = new TreeSet<UMLClassRenameDiff>(new ClassRenameComparator());
         for(Iterator<UMLClass> addedClassIterator = addedClasses.iterator(); addedClassIterator.hasNext();) {
            UMLClass addedClass = addedClassIterator.next();
            String renamedFile =  renamedFileHints.get(removedClass.getSourceFile());
            if(matcher.match(removedClass, addedClass, renamedFile)) {
               if(!conflictingMoveOfTopLevelClass(removedClass, addedClass) && !innerClassWithTheSameName(removedClass, addedClass)) {
            	   UMLClassRenameDiff classRenameDiff = new UMLClassRenameDiff(removedClass, addedClass, this);
            	   diffSet.add(classRenameDiff);
               }
            }
         }
         if(!diffSet.isEmpty()) {
            UMLClassRenameDiff minClassRenameDiff = diffSet.first();
            minClassRenameDiff.process();
            classRenameDiffList.add(minClassRenameDiff);
            addedClasses.remove(minClassRenameDiff.getRenamedClass());
            removedClassIterator.remove();
         }
      }
      
      List<UMLClassMoveDiff> allClassMoves = new ArrayList<UMLClassMoveDiff>(this.classMoveDiffList);
      Collections.sort(allClassMoves);
      
      for(UMLClassRenameDiff classRename : classRenameDiffList) {
         for(UMLClassMoveDiff classMove : allClassMoves) {
            if(classRename.isInnerClassMove(classMove)) {
               innerClassMoveDiffList.add(classMove);
            }
         }
      }
      this.classMoveDiffList.removeAll(innerClassMoveDiffList);
   }

   private boolean innerClassWithTheSameName(UMLClass removedClass, UMLClass addedClass) {
	   if(!removedClass.isTopLevel() && !addedClass.isTopLevel()) {
		   String removedClassName = removedClass.getName();
		   String removedName = removedClassName.substring(removedClassName.lastIndexOf(".")+1, removedClassName.length());
		   String addedClassName = addedClass.getName();
		   String addedName = addedClassName.substring(addedClassName.lastIndexOf(".")+1, addedClassName.length());
		   if(removedName.equals(addedName)) {
			   return true;
		   }
	   }
	   return false;
   }

   public List<UMLGeneralization> getAddedGeneralizations() {
      return addedGeneralizations;
   }

   public List<UMLRealization> getAddedRealizations() {
      return addedRealizations;
   }

   private List<MoveAttributeRefactoring> checkForAttributeMovesIncludingRemovedClasses() {
      List<UMLAttribute> addedAttributes = getAddedAttributesInCommonClasses();
      /*for(UMLClass addedClass : addedClasses) {
    	  addedAttributes.addAll(addedClass.getAttributes());
      }*/
      List<UMLAttribute> removedAttributes = getRemovedAttributesInCommonClasses();
      for(UMLClass removedClass : removedClasses) {
    	  removedAttributes.addAll(removedClass.getAttributes());
      }
      return checkForAttributeMoves(addedAttributes, removedAttributes);
   }

   private List<MoveAttributeRefactoring> checkForAttributeMovesIncludingAddedClasses() {
      List<UMLAttribute> addedAttributes = getAddedAttributesInCommonClasses();
      for(UMLClass addedClass : addedClasses) {
    	  addedAttributes.addAll(addedClass.getAttributes());
      }
      List<UMLAttribute> removedAttributes = getRemovedAttributesInCommonClasses();
      /*for(UMLClass removedClass : removedClasses) {
    	  removedAttributes.addAll(removedClass.getAttributes());
      }*/
      return checkForAttributeMoves(addedAttributes, removedAttributes);
   }

   private List<MoveAttributeRefactoring> checkForAttributeMovesBetweenCommonClasses() {
      List<UMLAttribute> addedAttributes = getAddedAttributesInCommonClasses();
      List<UMLAttribute> removedAttributes = getRemovedAttributesInCommonClasses();
      return checkForAttributeMoves(addedAttributes, removedAttributes);
   }

   private List<MoveAttributeRefactoring> checkForAttributeMovesBetweenRemovedAndAddedClasses() {
	   List<UMLAttribute> addedAttributes = new ArrayList<UMLAttribute>();
	   for(UMLClass addedClass : addedClasses) {
		   addedAttributes.addAll(addedClass.getAttributes());
	   }
	   List<UMLAttribute> removedAttributes = new ArrayList<UMLAttribute>();
	   for(UMLClass removedClass : removedClasses) {
		   removedAttributes.addAll(removedClass.getAttributes());
	   }
	   return checkForAttributeMoves(addedAttributes, removedAttributes);
   }

   private List<MoveAttributeRefactoring> checkForAttributeMoves(List<UMLAttribute> addedAttributes, List<UMLAttribute> removedAttributes) {
	   List<MoveAttributeRefactoring> refactorings = new ArrayList<MoveAttributeRefactoring>();
	   if(addedAttributes.size() <= removedAttributes.size()) {
		   for(UMLAttribute addedAttribute : addedAttributes) {
			   List<MoveAttributeRefactoring> candidates = new ArrayList<MoveAttributeRefactoring>();
			   for(UMLAttribute removedAttribute : removedAttributes) {
				   MoveAttributeRefactoring candidate = processPairOfAttributes(addedAttribute, removedAttribute);
				   if(candidate != null) {
					   candidates.add(candidate);
				   }
			   }
			   processCandidates(candidates, refactorings);
		   }
	   }
	   else {
		   for(UMLAttribute removedAttribute : removedAttributes) {
			   List<MoveAttributeRefactoring> candidates = new ArrayList<MoveAttributeRefactoring>();
			   for(UMLAttribute addedAttribute : addedAttributes) {
				   MoveAttributeRefactoring candidate = processPairOfAttributes(addedAttribute, removedAttribute);
				   if(candidate != null) {
					   candidates.add(candidate);
				   }
			   }
			   processCandidates(candidates, refactorings);
		   }
	   }
	   return refactorings;
   }

   private List<Refactoring> filterOutDuplicateRefactorings(Set<Refactoring> refactorings) {
	   List<Refactoring> filtered = new ArrayList<Refactoring>();
	   Map<String, List<Refactoring>> map = new LinkedHashMap<String, List<Refactoring>>();
	   for(Refactoring ref : refactorings) {
		   if(map.containsKey(ref.toString())) {
			   map.get(ref.toString()).add(ref);
		   }
		   else {
			   List<Refactoring> refs = new ArrayList<Refactoring>();
			   refs.add(ref);
			   map.put(ref.toString(), refs);
		   }
	   }
	   for(String key : map.keySet()) {
		   List<Refactoring> refs = map.get(key);
		   if(refs.size() == 1) {
			   filtered.addAll(refs);
		   }
		   else {
			   filtered.addAll(filterOutBasedOnFilePath(refs));
		   }
	   }
	   return filtered;
   }

   private List<Refactoring> filterOutBasedOnFilePath(List<Refactoring> refs) {
	   List<Refactoring> filtered = new ArrayList<Refactoring>();
	   Map<String, List<Refactoring>> groupBySourceFilePath = new LinkedHashMap<String, List<Refactoring>>();
	   for(Refactoring ref : refs) {
		   String sourceFilePath = ref.getInvolvedClassesBeforeRefactoring().iterator().next().getLeft();
		   if(groupBySourceFilePath.containsKey(sourceFilePath)) {
			   groupBySourceFilePath.get(sourceFilePath).add(ref);
		   }
		   else {
			   List<Refactoring> refs2 = new ArrayList<Refactoring>();
			   refs2.add(ref);
			   groupBySourceFilePath.put(sourceFilePath, refs2);
		   }
	   }
	   for(String sourceFilePath : groupBySourceFilePath.keySet()) {
		   List<Refactoring> sourceFilePathGroup = groupBySourceFilePath.get(sourceFilePath);
		   TreeMap<Integer, List<Refactoring>> groupByLongestCommonSourceFilePath = new TreeMap<Integer, List<Refactoring>>();
		   for(Refactoring ref : sourceFilePathGroup) {
			   String longestCommonFilePathPrefix = PrefixSuffixUtils.longestCommonPrefix(ref.getInvolvedClassesBeforeRefactoring().iterator().next().getLeft(),
					   ref.getInvolvedClassesAfterRefactoring().iterator().next().getLeft());
			   int length = longestCommonFilePathPrefix.length();
			   if(groupByLongestCommonSourceFilePath.containsKey(length)) {
				   groupByLongestCommonSourceFilePath.get(length).add(ref);
			   }
			   else {
				   List<Refactoring> refs2 = new ArrayList<Refactoring>();
				   refs2.add(ref);
				   groupByLongestCommonSourceFilePath.put(length, refs2);
			   }
		   }
		   filtered.addAll(groupByLongestCommonSourceFilePath.lastEntry().getValue());
	   }
	   return filtered;
   }

   private void processCandidates(List<MoveAttributeRefactoring> candidates, List<MoveAttributeRefactoring> refactorings) {
	   if(candidates.size() > 1) {
		   TreeMap<Integer, List<MoveAttributeRefactoring>> map = new TreeMap<Integer, List<MoveAttributeRefactoring>>();
		   for(MoveAttributeRefactoring candidate : candidates) {
			   int compatibility = computeCompatibility(candidate);
			   if(map.containsKey(compatibility)) {
				   map.get(compatibility).add(candidate);
			   }
			   else {
				   List<MoveAttributeRefactoring> refs = new ArrayList<MoveAttributeRefactoring>();
				   refs.add(candidate);
				   map.put(compatibility, refs);
			   }
		   }
		   int maxCompatibility = map.lastKey();
		   refactorings.addAll(map.get(maxCompatibility));
	   }
	   else if(candidates.size() == 1) {
		   refactorings.addAll(candidates);
	   }
   }

   private MoveAttributeRefactoring processPairOfAttributes(UMLAttribute addedAttribute, UMLAttribute removedAttribute) {
	   if(addedAttribute.getName().equals(removedAttribute.getName()) &&
			   addedAttribute.getType().equals(removedAttribute.getType())) {
		   if(isSubclassOf(removedAttribute.getClassName(), addedAttribute.getClassName())) {
			   PullUpAttributeRefactoring pullUpAttribute = new PullUpAttributeRefactoring(removedAttribute, addedAttribute);
			   return pullUpAttribute;
		   }
		   else if(isSubclassOf(addedAttribute.getClassName(), removedAttribute.getClassName())) {
			   PushDownAttributeRefactoring pushDownAttribute = new PushDownAttributeRefactoring(removedAttribute, addedAttribute);
			   return pushDownAttribute;
		   }
		   else if(sourceClassImportsTargetClass(removedAttribute.getClassName(), addedAttribute.getClassName()) ||
				   targetClassImportsSourceClass(removedAttribute.getClassName(), addedAttribute.getClassName())) {
			   if(!initializerContainsTypeLiteral(addedAttribute, removedAttribute)) {
				   MoveAttributeRefactoring moveAttribute = new MoveAttributeRefactoring(removedAttribute, addedAttribute);
				   return moveAttribute;
			   }
		   }
	   }
	   return null;
   }

   private boolean initializerContainsTypeLiteral(UMLAttribute addedAttribute, UMLAttribute removedAttribute) {
	   VariableDeclaration v1 = addedAttribute.getVariableDeclaration();
	   VariableDeclaration v2 = removedAttribute.getVariableDeclaration();
	   if(v1.getInitializer() != null && v2.getInitializer() != null) {
		   List<String> typeLiterals1 = v1.getInitializer().getTypeLiterals();
		   List<String> typeLiterals2 = v2.getInitializer().getTypeLiterals();
		   String className1 = addedAttribute.getNonQualifiedClassName();
		   String className2 = removedAttribute.getNonQualifiedClassName();
		   if(typeLiterals1.contains(className1 + ".class") && typeLiterals2.contains(className2 + ".class") &&
				   addedAttribute.getType().getClassType().endsWith("Logger") && removedAttribute.getType().getClassType().endsWith("Logger")) {
			   return true;
		   }
	   }
	   return false;
   }

   private int computeCompatibility(MoveAttributeRefactoring candidate) {
	   int count = 0;
	   for(Refactoring ref : refactorings) {
		   if(ref instanceof MoveOperationRefactoring) {
			   MoveOperationRefactoring moveRef = (MoveOperationRefactoring)ref;
			   if(moveRef.compatibleWith(candidate)) {
				   count++;
			   }
		   }
	   }
	   UMLClassBaseDiff sourceClassDiff = getUMLClassDiff(candidate.getSourceClassName());
	   UMLClassBaseDiff targetClassDiff = getUMLClassDiff(candidate.getTargetClassName());
	   if(sourceClassDiff != null) {
		   UMLType targetSuperclass = null;
		   if(targetClassDiff != null) {
			   targetSuperclass = targetClassDiff.getSuperclass();
		   }
		   List<UMLAttribute> addedAttributes = sourceClassDiff.getAddedAttributes();
		   for(UMLAttribute addedAttribute : addedAttributes) {
			   if(looksLikeSameType(addedAttribute.getType().getClassType(), candidate.getTargetClassName())) {
				   count++;
			   }
			   if(targetSuperclass != null && looksLikeSameType(addedAttribute.getType().getClassType(), targetSuperclass.getClassType())) {
				   count++;
			   }
		   }
		   List<UMLAttribute> originalAttributes = sourceClassDiff.originalClassAttributesOfType(candidate.getTargetClassName());
		   List<UMLAttribute> nextAttributes = sourceClassDiff.nextClassAttributesOfType(candidate.getTargetClassName());
		   if(targetSuperclass != null) {
			   originalAttributes.addAll(sourceClassDiff.originalClassAttributesOfType(targetSuperclass.getClassType()));
			   nextAttributes.addAll(sourceClassDiff.nextClassAttributesOfType(targetSuperclass.getClassType()));
		   }
		   Set<UMLAttribute> intersection = new LinkedHashSet<UMLAttribute>(originalAttributes);
		   intersection.retainAll(nextAttributes);
		   if(!intersection.isEmpty()) {
			   count++;
		   }
	   }
	   return count;
   }

   private boolean sourceClassImportsSuperclassOfTargetClass(String sourceClassName, String targetClassName) {
	   UMLClassBaseDiff targetClassDiff = getUMLClassDiff(targetClassName);
	   if(targetClassDiff != null && targetClassDiff.getSuperclass() != null) {
		   UMLClassBaseDiff superclassOfTargetClassDiff = getUMLClassDiff(targetClassDiff.getSuperclass());
		   if(superclassOfTargetClassDiff != null) {
			   return sourceClassImportsTargetClass(sourceClassName, superclassOfTargetClassDiff.getNextClassName());
		   }
	   }
	   return false;
   }

   private boolean sourceClassImportsTargetClass(String sourceClassName, String targetClassName) {
	   UMLClassBaseDiff classDiff = getUMLClassDiff(sourceClassName);
	   if(classDiff == null) {
		   classDiff = getUMLClassDiff(UMLType.extractTypeObject(sourceClassName));
	   }
	   if(classDiff != null) {
		   return classDiff.nextClassImportsType(targetClassName) || classDiff.originalClassImportsType(targetClassName);
	   }
	   UMLClass removedClass = getRemovedClass(sourceClassName);
	   if(removedClass == null) {
		   removedClass = looksLikeRemovedClass(UMLType.extractTypeObject(sourceClassName));
	   }
	   if(removedClass != null) {
		   return removedClass.importsType(targetClassName);
	   }
	   return false;
   }

   private boolean targetClassImportsSourceClass(String sourceClassName, String targetClassName) {
	   UMLClassBaseDiff classDiff = getUMLClassDiff(targetClassName);
	   if(classDiff == null) {
		   classDiff = getUMLClassDiff(UMLType.extractTypeObject(targetClassName));
	   }
	   if(classDiff != null) {
		   return classDiff.originalClassImportsType(sourceClassName) || classDiff.nextClassImportsType(sourceClassName);
	   }
	   UMLClass addedClass = getAddedClass(targetClassName);
	   if(addedClass == null) {
		   addedClass = looksLikeAddedClass(UMLType.extractTypeObject(targetClassName));
	   }
	   if(addedClass != null) {
		   return addedClass.importsType(sourceClassName);
	   }
	   return false;
   }

   private List<UMLAttribute> getAddedAttributesInCommonClasses() {
      List<UMLAttribute> addedAttributes = new ArrayList<UMLAttribute>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
         addedAttributes.addAll(classDiff.getAddedAttributes());
      }
      return addedAttributes;
   }

   private List<UMLAttribute> getRemovedAttributesInCommonClasses() {
      List<UMLAttribute> removedAttributes = new ArrayList<UMLAttribute>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
         removedAttributes.addAll(classDiff.getRemovedAttributes());
      }
      return removedAttributes;
   }

   private List<UMLOperation> getAddedOperationsInCommonClasses() {
      List<UMLOperation> addedOperations = new ArrayList<UMLOperation>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
         addedOperations.addAll(classDiff.getAddedOperations());
      }
      return addedOperations;
   }

   private List<UMLOperation> getAddedAndExtractedOperationsInCommonClasses() {
      List<UMLOperation> addedOperations = new ArrayList<UMLOperation>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
         addedOperations.addAll(classDiff.getAddedOperations());
         for(Refactoring ref : classDiff.getRefactorings()) {
        	 if(ref instanceof ExtractOperationRefactoring) {
        		 ExtractOperationRefactoring extractRef = (ExtractOperationRefactoring)ref;
        		 addedOperations.add(extractRef.getExtractedOperation());
        	 }
         }
      }
      return addedOperations;
   }

   private List<UMLOperation> getAddedOperationsInMovedAndRenamedClasses() {
      List<UMLOperation> addedOperations = new ArrayList<UMLOperation>();
      for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
         addedOperations.addAll(classDiff.getAddedOperations());
      }
      for(UMLClassMoveDiff classDiff : classMoveDiffList) {
         addedOperations.addAll(classDiff.getAddedOperations());
      }
      for(UMLClassRenameDiff classDiff : classRenameDiffList) {
         addedOperations.addAll(classDiff.getAddedOperations());
      }
      return addedOperations;
   }

   private List<UMLOperation> getRemovedOperationsInCommonClasses() {
      List<UMLOperation> removedOperations = new ArrayList<UMLOperation>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
         removedOperations.addAll(classDiff.getRemovedOperations());
      }
      return removedOperations;
   }

   private List<UMLOperation> getRemovedOperationsInCommonMovedRenamedClasses() {
      List<UMLOperation> removedOperations = new ArrayList<UMLOperation>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
         removedOperations.addAll(classDiff.getRemovedOperations());
      }
      for(UMLClassMoveDiff classDiff : classMoveDiffList) {
    	  removedOperations.addAll(classDiff.getRemovedOperations());
      }
      for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
    	  removedOperations.addAll(classDiff.getRemovedOperations());
      }
      for(UMLClassRenameDiff classDiff : classRenameDiffList) {
    	  removedOperations.addAll(classDiff.getRemovedOperations());
      }
      return removedOperations;
   }

   private List<UMLOperation> getRemovedAndInlinedOperationsInCommonClasses() {
      List<UMLOperation> removedOperations = new ArrayList<UMLOperation>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
         removedOperations.addAll(classDiff.getRemovedOperations());
         for(Refactoring ref : classDiff.getRefactorings()) {
        	 if(ref instanceof InlineOperationRefactoring) {
        		 InlineOperationRefactoring extractRef = (InlineOperationRefactoring)ref;
        		 removedOperations.add(extractRef.getInlinedOperation());
        	 }
         }
      }
      return removedOperations;
   }
   
   private List<UMLOperationBodyMapper> getOperationBodyMappersInCommonClasses() {
      List<UMLOperationBodyMapper> mappers = new ArrayList<UMLOperationBodyMapper>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
         mappers.addAll(classDiff.getOperationBodyMapperList());
      }
      return mappers;
   }

   private List<UMLOperationBodyMapper> getOperationBodyMappersInMovedAndRenamedClasses() {
	   List<UMLOperationBodyMapper> mappers = new ArrayList<UMLOperationBodyMapper>();
	   for(UMLClassMoveDiff classDiff : classMoveDiffList) {
		   mappers.addAll(classDiff.getOperationBodyMapperList());
	   }
	   for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
		   mappers.addAll(classDiff.getOperationBodyMapperList());
	   }
	   for(UMLClassRenameDiff classDiff : classRenameDiffList) {
		   mappers.addAll(classDiff.getOperationBodyMapperList());
	   }
	   return mappers;
   }

   private List<ExtractClassRefactoring> identifyExtractClassRefactorings(List<? extends UMLClassBaseDiff> classDiffs) throws RefactoringMinerTimedOutException {
	   List<ExtractClassRefactoring> refactorings = new ArrayList<ExtractClassRefactoring>();
	   for(UMLClass addedClass : addedClasses) {
		   TreeSet<CandidateExtractClassRefactoring> candidates = new TreeSet<CandidateExtractClassRefactoring>();
		   UMLType addedClassSuperType = addedClass.getSuperclass();
		   if(!addedClass.isInterface()) {
			   for(UMLClassBaseDiff classDiff : classDiffs) {
				   UMLType classDiffSuperType = classDiff.getNewSuperclass();
				   boolean commonSuperType = addedClassSuperType != null && classDiffSuperType != null &&
						   addedClassSuperType.getClassType().equals(classDiffSuperType.getClassType());
				   boolean commonInterface = false;
				   for(UMLType addedClassInterface : addedClass.getImplementedInterfaces()) {
					   for(UMLType classDiffInterface : classDiff.getNextClass().getImplementedInterfaces()) {
						   if(addedClassInterface.getClassType().equals(classDiffInterface.getClassType())) {
							   commonInterface = true;
							   break;
						   }
					   }
					   if(commonInterface)
						   break;
				   }
				   boolean extendsAddedClass = classDiff.getNewSuperclass() != null &&
						   addedClass.getName().endsWith("." + classDiff.getNewSuperclass().getClassType());
				   UMLAttribute attributeOfExtractedClassType = attributeOfExtractedClassType(addedClass, classDiff);
				   boolean isTestClass =  addedClass.isTestClass() && classDiff.getOriginalClass().isTestClass();
				   if((!commonSuperType && !commonInterface && !extendsAddedClass) || attributeOfExtractedClassType != null || isTestClass) {
					   ExtractClassRefactoring refactoring = atLeastOneCommonAttributeOrOperation(addedClass, classDiff, attributeOfExtractedClassType);
					   if(refactoring != null) {
						   CandidateExtractClassRefactoring candidate = new CandidateExtractClassRefactoring(classDiff, refactoring);
						   candidates.add(candidate);
					   }
				   }
			   }
		   }
		   if(!candidates.isEmpty()) {
			   CandidateExtractClassRefactoring firstCandidate = candidates.first();
			   if(firstCandidate.innerClassExtract() || firstCandidate.subclassExtract()) {
				   detectSubRefactorings(firstCandidate.getClassDiff(),
						   firstCandidate.getRefactoring().getExtractedClass(),
						   firstCandidate.getRefactoring().getRefactoringType());
				   refactorings.add(firstCandidate.getRefactoring());
			   }
			   else {
				   for(CandidateExtractClassRefactoring candidate : candidates) {
					   detectSubRefactorings(candidate.getClassDiff(),
							   candidate.getRefactoring().getExtractedClass(),
							   candidate.getRefactoring().getRefactoringType());
					   refactorings.add(candidate.getRefactoring());
				   }
			   }
		   }
	   }
	   return refactorings;
   }

   private UMLAttribute attributeOfExtractedClassType(UMLClass umlClass, UMLClassBaseDiff classDiff) {
	   List<UMLAttribute> addedAttributes = classDiff.getAddedAttributes();
	   for(UMLAttribute addedAttribute : addedAttributes) {
		   if(umlClass.getName().endsWith("." + addedAttribute.getType().getClassType())) {
			   return addedAttribute;
		   }
	   }
	   return null;
   }

   private ExtractClassRefactoring atLeastOneCommonAttributeOrOperation(UMLClass umlClass, UMLClassBaseDiff classDiff, UMLAttribute attributeOfExtractedClassType) {
	   Set<UMLOperation> commonOperations = new LinkedHashSet<UMLOperation>();
	   for(UMLOperation operation : classDiff.getRemovedOperations()) {
		   if(!operation.isConstructor() && !operation.overridesObject()) {
			   if(umlClass.containsOperationWithTheSameSignatureIgnoringChangedTypes(operation)) {
				   commonOperations.add(operation);
			   }
		   }
	   }
	   Set<UMLAttribute> commonAttributes = new LinkedHashSet<UMLAttribute>();
	   for(UMLAttribute attribute : classDiff.getRemovedAttributes()) {
		   if(umlClass.containsAttributeWithTheSameNameIgnoringChangedType(attribute)) {
			   commonAttributes.add(attribute);
		   }
	   }
	   int threshold = 1;
	   if(attributeOfExtractedClassType != null)
		   threshold = 0;
	   if(commonOperations.size() > threshold || commonAttributes.size() > threshold) {
		   return new ExtractClassRefactoring(umlClass, classDiff, commonOperations, commonAttributes, attributeOfExtractedClassType);
	   }
	   return null;
   }

   private List<ExtractSuperclassRefactoring> identifyExtractSuperclassRefactorings() throws RefactoringMinerTimedOutException {
      List<ExtractSuperclassRefactoring> refactorings = new ArrayList<ExtractSuperclassRefactoring>();
      for(UMLClass addedClass : addedClasses) {
         Set<UMLClass> subclassSet = new LinkedHashSet<UMLClass>();
         String addedClassName = addedClass.getName();
         for(UMLGeneralization addedGeneralization : addedGeneralizations) {
        	 processAddedGeneralization(addedClass, subclassSet, addedGeneralization);
         }
         for(UMLGeneralizationDiff generalizationDiff : generalizationDiffList) {
        	 UMLGeneralization addedGeneralization = generalizationDiff.getAddedGeneralization();
        	 UMLGeneralization removedGeneralization = generalizationDiff.getRemovedGeneralization();
        	 if(!addedGeneralization.getParent().equals(removedGeneralization.getParent())) {
        		 processAddedGeneralization(addedClass, subclassSet, addedGeneralization);
        	 }
         }
         for(UMLRealization addedRealization : addedRealizations) {
            String supplier = addedRealization.getSupplier();
			if(looksLikeSameType(supplier, addedClassName) && topLevelOrSameOuterClass(addedClass, addedRealization.getClient()) && getAddedClass(addedRealization.getClient().getName()) == null) {
               UMLClassBaseDiff clientClassDiff = getUMLClassDiff(addedRealization.getClient().getName());
               int implementedInterfaceOperations = 0;
               boolean clientImplementsSupplier = false;
               if(clientClassDiff != null) {
                  for(UMLOperation interfaceOperation : addedClass.getOperations()) {
                     if(clientClassDiff.containsOperationWithTheSameSignatureInOriginalClass(interfaceOperation)) {
                        implementedInterfaceOperations++;
                     }
                  }
                  clientImplementsSupplier = clientClassDiff.getOriginalClass().getImplementedInterfaces().contains(UMLType.extractTypeObject(supplier));
               }
               if((implementedInterfaceOperations > 0 || addedClass.getOperations().size() == 0) && !clientImplementsSupplier)
                  subclassSet.add(addedRealization.getClient());
            }
         }
         if(subclassSet.size() > 0) {
            ExtractSuperclassRefactoring extractSuperclassRefactoring = new ExtractSuperclassRefactoring(addedClass, subclassSet);
            refactorings.add(extractSuperclassRefactoring);
         }
      }
      return refactorings;
   }

   private void processAddedGeneralization(UMLClass addedClass, Set<UMLClass> subclassSet, UMLGeneralization addedGeneralization) throws RefactoringMinerTimedOutException {
	   String parent = addedGeneralization.getParent();
	   UMLClass subclass = addedGeneralization.getChild();
	   if(looksLikeSameType(parent, addedClass.getName()) && topLevelOrSameOuterClass(addedClass, subclass) && getAddedClass(subclass.getName()) == null) {
		   UMLClassBaseDiff subclassDiff = getUMLClassDiff(subclass.getName());
		   if(subclassDiff != null) {
			   detectSubRefactorings(subclassDiff, addedClass, RefactoringType.EXTRACT_SUPERCLASS);
		   }
		   subclassSet.add(subclass);
	   }
   }

   private void detectSubRefactorings(UMLClassBaseDiff classDiff, UMLClass addedClass, RefactoringType parentType) throws RefactoringMinerTimedOutException {
	   for(UMLOperation addedOperation : addedClass.getOperations()) {
		   UMLOperation removedOperation = classDiff.containsRemovedOperationWithTheSameSignature(addedOperation);
		   if(removedOperation != null) {
			   classDiff.getRemovedOperations().remove(removedOperation);
			   Refactoring ref = null;
			   if(parentType.equals(RefactoringType.EXTRACT_SUPERCLASS)) {
				   ref = new PullUpOperationRefactoring(removedOperation, addedOperation);
			   }
			   else if(parentType.equals(RefactoringType.EXTRACT_CLASS)) {
				   ref = new MoveOperationRefactoring(removedOperation, addedOperation);
			   }
			   else if(parentType.equals(RefactoringType.EXTRACT_SUBCLASS)) {
				   ref = new PushDownOperationRefactoring(removedOperation, addedOperation);
			   }
			   this.refactorings.add(ref);
			   UMLOperationBodyMapper mapper = new UMLOperationBodyMapper(removedOperation, addedOperation, classDiff);
			   UMLOperationDiff operationSignatureDiff = new UMLOperationDiff(removedOperation, addedOperation, mapper.getMappings());
			   refactorings.addAll(operationSignatureDiff.getRefactorings());
			   checkForExtractedOperationsWithinMovedMethod(mapper, addedClass);
		   }
	   }
	   for(UMLAttribute addedAttribute : addedClass.getAttributes()) {
		   UMLAttribute removedAttribute = classDiff.containsRemovedAttributeWithTheSameSignature(addedAttribute);
		   if(removedAttribute != null) {
			   classDiff.getRemovedAttributes().remove(removedAttribute);
			   Refactoring ref = null;
			   if(parentType.equals(RefactoringType.EXTRACT_SUPERCLASS)) {
				   ref = new PullUpAttributeRefactoring(removedAttribute, addedAttribute);
			   }
			   else if(parentType.equals(RefactoringType.EXTRACT_CLASS)) {
				   ref = new MoveAttributeRefactoring(removedAttribute, addedAttribute);
			   }
			   else if(parentType.equals(RefactoringType.EXTRACT_SUBCLASS)) {
				   ref = new PushDownAttributeRefactoring(removedAttribute, addedAttribute);
			   }
			   this.refactorings.add(ref);
		   }
	   }
   }

   private void checkForExtractedOperationsWithinMovedMethod(UMLOperationBodyMapper movedMethodMapper, UMLClass addedClass) throws RefactoringMinerTimedOutException {
	   UMLOperation removedOperation = movedMethodMapper.getOperation1();
	   UMLOperation addedOperation = movedMethodMapper.getOperation2();
	   List<OperationInvocation> removedInvocations = removedOperation.getAllOperationInvocations();
	   List<OperationInvocation> addedInvocations = addedOperation.getAllOperationInvocations();
	   Set<OperationInvocation> intersection = new LinkedHashSet<OperationInvocation>(removedInvocations);
	   intersection.retainAll(addedInvocations);
	   Set<OperationInvocation> newInvocations = new LinkedHashSet<OperationInvocation>(addedInvocations);
	   newInvocations.removeAll(intersection);
	   for(OperationInvocation newInvocation : newInvocations) {
		   for(UMLOperation operation : addedClass.getOperations()) {
			   if(!operation.isAbstract() && !operation.hasEmptyBody() &&
					   newInvocation.matchesOperation(operation, addedOperation.variableTypeMap(), this)) {
				   ExtractOperationDetection detection = new ExtractOperationDetection(movedMethodMapper, addedClass.getOperations(), getUMLClassDiff(operation.getClassName()), this);
				   List<ExtractOperationRefactoring> refs = detection.check(operation);
				   this.refactorings.addAll(refs);
			   }
		   }
	   }
   }

   private boolean topLevelOrSameOuterClass(UMLClass class1, UMLClass class2) {
	   if(!class1.isTopLevel() && !class2.isTopLevel()) {
		   return class1.getPackageName().equals(class2.getPackageName());
	   }
	   return true;
   }

   public static boolean looksLikeSameType(String parent, String addedClassName) {
      if (addedClassName.contains(".") && !parent.contains(".")) {
         return parent.equals(addedClassName.substring(addedClassName.lastIndexOf(".") + 1));
      }
      if (parent.contains(".") && !addedClassName.contains(".")) {
         return addedClassName.equals(parent.substring(parent.lastIndexOf(".") + 1));
      }
      if (parent.contains(".") && addedClassName.contains(".")) {
    	  return UMLType.extractTypeObject(parent).equalClassType(UMLType.extractTypeObject(addedClassName));
      }
      return parent.equals(addedClassName);
   }

   private List<ConvertAnonymousClassToTypeRefactoring> identifyConvertAnonymousClassToTypeRefactorings() {
      List<ConvertAnonymousClassToTypeRefactoring> refactorings = new ArrayList<ConvertAnonymousClassToTypeRefactoring>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
	      for(UMLAnonymousClass anonymousClass : classDiff.getRemovedAnonymousClasses()) {
	         for(UMLClass addedClass : addedClasses) {
	            if(addedClass.getAttributes().containsAll(anonymousClass.getAttributes()) &&
	                  addedClass.getOperations().containsAll(anonymousClass.getOperations())) {
	               ConvertAnonymousClassToTypeRefactoring refactoring = new ConvertAnonymousClassToTypeRefactoring(anonymousClass, addedClass);
	               refactorings.add(refactoring);
	            }
	         }
	      }
      }
      return refactorings;
   }

   private List<Refactoring> getMoveClassRefactorings() {
	   List<Refactoring> refactorings = new ArrayList<Refactoring>();
	   List<RenamePackageRefactoring> renamePackageRefactorings = new ArrayList<RenamePackageRefactoring>();
	   List<MoveSourceFolderRefactoring> moveSourceFolderRefactorings = new ArrayList<MoveSourceFolderRefactoring>();
	   for(UMLClassMoveDiff classMoveDiff : classMoveDiffList) {
		   UMLClass originalClass = classMoveDiff.getOriginalClass();
		   String originalName = originalClass.getName();
		   UMLClass movedClass = classMoveDiff.getMovedClass();
		   String movedName = movedClass.getName();
		   
		   String originalPath = originalClass.getSourceFile();
		   String movedPath = movedClass.getSourceFile();
		   String originalPathPrefix = "";
		   if(originalPath.contains("/")) {
			   originalPathPrefix = originalPath.substring(0, originalPath.lastIndexOf('/'));
		   }
		   String movedPathPrefix = "";
		   if(movedPath.contains("/")) {
			   movedPathPrefix = movedPath.substring(0, movedPath.lastIndexOf('/'));
		   }
		   
		   if (!originalName.equals(movedName)) {
			   MoveClassRefactoring refactoring = new MoveClassRefactoring(originalClass, movedClass);
			   RenamePattern renamePattern = refactoring.getRenamePattern();
			   //check if the the original path is a substring of the moved path and vice versa
			   if(renamePattern.getBefore().contains(renamePattern.getAfter()) ||
					   renamePattern.getAfter().contains(renamePattern.getBefore()) ||
					   !originalClass.isTopLevel() || !movedClass.isTopLevel()) {
				   refactorings.add(refactoring);
			   }
			   else {
				   boolean foundInMatchingRenamePackageRefactoring = false;
				   for(RenamePackageRefactoring renamePackageRefactoring : renamePackageRefactorings) {
					   if(renamePackageRefactoring.getPattern().equals(renamePattern)) {
						   renamePackageRefactoring.addMoveClassRefactoring(refactoring);
						   foundInMatchingRenamePackageRefactoring = true;
						   break;
					   }
				   }
				   if(!foundInMatchingRenamePackageRefactoring) {
					   renamePackageRefactorings.add(new RenamePackageRefactoring(refactoring));
				   }
			   }
		   } else if(!originalPathPrefix.equals(movedPathPrefix)) {
			   MovedClassToAnotherSourceFolder refactoring = new MovedClassToAnotherSourceFolder(originalClass, movedClass, originalPathPrefix, movedPathPrefix);
			   RenamePattern renamePattern = refactoring.getRenamePattern();
			   boolean foundInMatchingMoveSourceFolderRefactoring = false;
			   for(MoveSourceFolderRefactoring moveSourceFolderRefactoring : moveSourceFolderRefactorings) {
				   if(moveSourceFolderRefactoring.getPattern().equals(renamePattern)) {
					   moveSourceFolderRefactoring.addMovedClassToAnotherSourceFolder(refactoring);
					   foundInMatchingMoveSourceFolderRefactoring = true;
					   break;
				   }
			   }
			   if(!foundInMatchingMoveSourceFolderRefactoring) {
				   moveSourceFolderRefactorings.add(new MoveSourceFolderRefactoring(refactoring));
			   }
		   }
	   }
	   for(RenamePackageRefactoring renamePackageRefactoring : renamePackageRefactorings) {
		   List<MoveClassRefactoring> moveClassRefactorings = renamePackageRefactoring.getMoveClassRefactorings();
		   if(moveClassRefactorings.size() > 1 && isSourcePackageDeleted(renamePackageRefactoring)) {
			   refactorings.add(renamePackageRefactoring);
		   }
		   //else {
			   refactorings.addAll(moveClassRefactorings);
		   //}
	   }
	   refactorings.addAll(moveSourceFolderRefactorings);
	   return refactorings;
   }

   private boolean isSourcePackageDeleted(RenamePackageRefactoring renamePackageRefactoring) {
	   for(String deletedFolderPath : deletedFolderPaths) {
		   String originalPath = renamePackageRefactoring.getPattern().getBefore();
		   //remove last .
		   String trimmedOriginalPath = originalPath.endsWith(".") ? originalPath.substring(0, originalPath.length()-1) : originalPath;
		   String convertedPackageToFilePath = trimmedOriginalPath.replaceAll("\\.", "/");
		   if(deletedFolderPath.endsWith(convertedPackageToFilePath)) {
			   return true;
		   }
	   }
	   return false;
   }

   private List<Refactoring> getRenameClassRefactorings() {
      List<Refactoring> refactorings = new ArrayList<Refactoring>();
      for(UMLClassRenameDiff classRenameDiff : classRenameDiffList) {
    	  Refactoring refactoring = null;
    	  if(classRenameDiff.samePackage())
    		  refactoring = new RenameClassRefactoring(classRenameDiff.getOriginalClass(), classRenameDiff.getRenamedClass());
    	  else
    		  refactoring = new MoveAndRenameClassRefactoring(classRenameDiff.getOriginalClass(), classRenameDiff.getRenamedClass());
         refactorings.add(refactoring);
      }
      return refactorings;
   }

   public List<Refactoring> getRefactorings() throws RefactoringMinerTimedOutException {
      Set<Refactoring> refactorings = new LinkedHashSet<Refactoring>();
      refactorings.addAll(getMoveClassRefactorings());
      refactorings.addAll(getRenameClassRefactorings());
      refactorings.addAll(identifyConvertAnonymousClassToTypeRefactorings());
      Map<Replacement, Set<CandidateAttributeRefactoring>> renameMap = new LinkedHashMap<Replacement, Set<CandidateAttributeRefactoring>>();
      Map<MergeVariableReplacement, Set<CandidateMergeVariableRefactoring>> mergeMap = new LinkedHashMap<MergeVariableReplacement, Set<CandidateMergeVariableRefactoring>>();
      for(UMLClassDiff classDiff : commonClassDiffList) {
         refactorings.addAll(classDiff.getRefactorings());
         extractMergePatterns(classDiff, mergeMap);
		 extractRenamePatterns(classDiff, renameMap);
      }
      for(UMLClassMoveDiff classDiff : classMoveDiffList) {
         refactorings.addAll(classDiff.getRefactorings());
         extractMergePatterns(classDiff, mergeMap);
		 extractRenamePatterns(classDiff, renameMap);
      }
      for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
         refactorings.addAll(classDiff.getRefactorings());
         extractMergePatterns(classDiff, mergeMap);
		 extractRenamePatterns(classDiff, renameMap);
      }
      for(UMLClassRenameDiff classDiff : classRenameDiffList) {
         refactorings.addAll(classDiff.getRefactorings());
         extractMergePatterns(classDiff, mergeMap);
		 extractRenamePatterns(classDiff, renameMap);
      }
      Map<RenamePattern, Integer> typeRenamePatternMap = typeRenamePatternMap(refactorings);
      for(RenamePattern pattern : typeRenamePatternMap.keySet()) {
    	  if(typeRenamePatternMap.get(pattern) > 1) {
    		  UMLClass removedClass = looksLikeRemovedClass(UMLType.extractTypeObject(pattern.getBefore()));
    		  UMLClass addedClass = looksLikeAddedClass(UMLType.extractTypeObject(pattern.getAfter()));
    		  if(removedClass != null && addedClass != null) {
    			  UMLClassRenameDiff renameDiff = new UMLClassRenameDiff(removedClass, addedClass, this);
    			  renameDiff.process();
    			  refactorings.addAll(renameDiff.getRefactorings());
    			  extractMergePatterns(renameDiff, mergeMap);
    			  extractRenamePatterns(renameDiff, renameMap);
    			  classRenameDiffList.add(renameDiff);
    			  Refactoring refactoring = null;
    			  if(renameDiff.samePackage())
    	    		  refactoring = new RenameClassRefactoring(renameDiff.getOriginalClass(), renameDiff.getRenamedClass());
    	    	  else
    	    		  refactoring = new MoveAndRenameClassRefactoring(renameDiff.getOriginalClass(), renameDiff.getRenamedClass());
    			  refactorings.add(refactoring);
    		  }
    	  }
      }
      for(MergeVariableReplacement merge : mergeMap.keySet()) {
    	  UMLClassBaseDiff diff = null;
    	  for(String mergedVariable : merge.getMergedVariables()) {
    		  Replacement replacement = new Replacement(mergedVariable, merge.getAfter(), ReplacementType.VARIABLE_NAME);
    		  diff = getUMLClassDiffWithAttribute(replacement);
    	  }
    	  if(diff != null) {
    		  Set<UMLAttribute> mergedAttributes = new LinkedHashSet<UMLAttribute>();
    		  Set<VariableDeclaration> mergedVariables = new LinkedHashSet<VariableDeclaration>();
    		  for(String mergedVariable : merge.getMergedVariables()) {
    			  UMLAttribute a1 = diff.findAttributeInOriginalClass(mergedVariable);
    			  if(a1 != null) {
    				  mergedAttributes.add(a1);
    				  mergedVariables.add(a1.getVariableDeclaration());
    			  }
    		  }
    		  UMLAttribute a2 = diff.findAttributeInNextClass(merge.getAfter());
    		  Set<CandidateMergeVariableRefactoring> set = mergeMap.get(merge);
    		  if(mergedVariables.size() > 1 && mergedVariables.size() == merge.getMergedVariables().size() && a2 != null) {
    			  MergeAttributeRefactoring ref = new MergeAttributeRefactoring(mergedAttributes, a2, diff.getOriginalClassName(), diff.getNextClassName(), set);
    			  if(!refactorings.contains(ref)) {
    				  refactorings.add(ref);
    				  Refactoring conflictingRefactoring = attributeRenamed(mergedVariables, a2.getVariableDeclaration(), refactorings);
    				  if(conflictingRefactoring != null) {
    					  refactorings.remove(conflictingRefactoring);
    				  }
    			  }
    		  }
    	  }
      }
	  for(Replacement pattern : renameMap.keySet()) {
		 UMLClassBaseDiff diff = getUMLClassDiffWithAttribute(pattern);
		 Set<CandidateAttributeRefactoring> set = renameMap.get(pattern);
		 for(CandidateAttributeRefactoring candidate : set) {
			 if(candidate.getOriginalVariableDeclaration() == null && candidate.getRenamedVariableDeclaration() == null) {
				 if(diff != null) {
					 UMLAttribute a1 = diff.findAttributeInOriginalClass(pattern.getBefore());
					 UMLAttribute a2 = diff.findAttributeInNextClass(pattern.getAfter());
					 if(!diff.getOriginalClass().containsAttributeWithName(pattern.getAfter()) &&
								!diff.getNextClass().containsAttributeWithName(pattern.getBefore()) &&
								!attributeMerged(a1, a2, refactorings)) {
						 UMLAttributeDiff attributeDiff = new UMLAttributeDiff(a1, a2, diff.getOperationBodyMapperList());
						 Set<Refactoring> attributeDiffRefactorings = attributeDiff.getRefactorings(set);
						 if(!refactorings.containsAll(attributeDiffRefactorings)) {
							 refactorings.addAll(attributeDiffRefactorings);
							 break;//it's not necessary to repeat the same process for all candidates in the set
						 }
					 }
				 }
			 }
			 else if(candidate.getOriginalVariableDeclaration() != null) {
				 List<UMLClassBaseDiff> diffs1 = getUMLClassDiffWithExistingAttributeAfter(pattern);
				 List<UMLClassBaseDiff> diffs2 = getUMLClassDiffWithNewAttributeAfter(pattern);
				 if(!diffs1.isEmpty()) {
					 UMLClassBaseDiff diff1 = diffs1.get(0);
					 UMLClassBaseDiff originalClassDiff = null;
					 if(candidate.getOriginalAttribute() != null) {
						 originalClassDiff = getUMLClassDiff(candidate.getOriginalAttribute().getClassName()); 
					 }
					 else {
						 originalClassDiff = getUMLClassDiff(candidate.getOperationBefore().getClassName());
					 }
					 if(diffs1.size() > 1) {
						 for(UMLClassBaseDiff classDiff : diffs1) {
							 if(isSubclassOf(originalClassDiff.nextClass.getName(), classDiff.nextClass.getName())) {
								 diff1 = classDiff;
								 break;
							 }
						 }
					 }
					 UMLAttribute a2 = diff1.findAttributeInNextClass(pattern.getAfter());
					 if(a2 != null) {
						 if(candidate.getOriginalVariableDeclaration().isAttribute()) {
							 if(originalClassDiff != null && originalClassDiff.removedAttributes.contains(candidate.getOriginalAttribute())) {
								 ReplaceAttributeRefactoring ref = new ReplaceAttributeRefactoring(candidate.getOriginalAttribute(), a2, set);
								 if(!refactorings.contains(ref)) {
									 refactorings.add(ref);
									 break;//it's not necessary to repeat the same process for all candidates in the set
								 }
							 }
						 }
						 else {
							 RenameVariableRefactoring ref = new RenameVariableRefactoring(candidate.getOriginalVariableDeclaration(), a2.getVariableDeclaration(), candidate.getOperationBefore(), candidate.getOperationAfter(), candidate.getAttributeReferences());
							 if(!refactorings.contains(ref)) {
								 refactorings.add(ref);
								 break;//it's not necessary to repeat the same process for all candidates in the set
							 }
						 }
					 }
				 }
				 else if(!diffs2.isEmpty()) {
					 UMLClassBaseDiff diff2 = diffs2.get(0);
					 UMLClassBaseDiff originalClassDiff = null;
					 if(candidate.getOriginalAttribute() != null) {
						 originalClassDiff = getUMLClassDiff(candidate.getOriginalAttribute().getClassName()); 
					 }
					 else {
						 originalClassDiff = getUMLClassDiff(candidate.getOperationBefore().getClassName());
					 }
					 if(diffs2.size() > 1) {
						 for(UMLClassBaseDiff classDiff : diffs2) {
							 if(isSubclassOf(originalClassDiff.nextClass.getName(), classDiff.nextClass.getName())) {
								 diff2 = classDiff;
								 break;
							 }
						 }
					 }
					 UMLAttribute a2 = diff2.findAttributeInNextClass(pattern.getAfter());
					 if(a2 != null) {
						 if(candidate.getOriginalVariableDeclaration().isAttribute()) {
							 if(originalClassDiff != null && originalClassDiff.removedAttributes.contains(candidate.getOriginalAttribute())) {
								 MoveAndRenameAttributeRefactoring ref = new MoveAndRenameAttributeRefactoring(candidate.getOriginalAttribute(), a2, set);
								 if(!refactorings.contains(ref)) {
									 refactorings.add(ref);
									 break;//it's not necessary to repeat the same process for all candidates in the set
								 }
							 }
						 }
						 else {
							 RenameVariableRefactoring ref = new RenameVariableRefactoring(candidate.getOriginalVariableDeclaration(), a2.getVariableDeclaration(), candidate.getOperationBefore(), candidate.getOperationAfter(), candidate.getAttributeReferences());
							 if(!refactorings.contains(ref)) {
								 refactorings.add(ref);
								 break;//it's not necessary to repeat the same process for all candidates in the set
							 }
						 }
					 }
				 }
			 }
		 }
	  }
	  refactorings.addAll(identifyExtractSuperclassRefactorings());
	  refactorings.addAll(identifyExtractClassRefactorings(commonClassDiffList));
      refactorings.addAll(identifyExtractClassRefactorings(classMoveDiffList));
      refactorings.addAll(identifyExtractClassRefactorings(innerClassMoveDiffList));
      refactorings.addAll(identifyExtractClassRefactorings(classRenameDiffList));
      checkForOperationMovesBetweenCommonClasses();
      checkForOperationMovesIncludingAddedClasses();
      checkForOperationMovesIncludingRemovedClasses();
      checkForExtractedAndMovedOperations(getOperationBodyMappersInCommonClasses(), getAddedAndExtractedOperationsInCommonClasses());
      checkForExtractedAndMovedOperations(getOperationBodyMappersInMovedAndRenamedClasses(), getAddedOperationsInMovedAndRenamedClasses());
      checkForMovedAndInlinedOperations(getOperationBodyMappersInCommonClasses(), getRemovedAndInlinedOperationsInCommonClasses());
      refactorings.addAll(checkForAttributeMovesBetweenCommonClasses());
      refactorings.addAll(checkForAttributeMovesIncludingAddedClasses());
      refactorings.addAll(checkForAttributeMovesIncludingRemovedClasses());
      refactorings.addAll(this.refactorings);
      for(UMLClassDiff classDiff : commonClassDiffList) {
    	  inferMethodSignatureRelatedRefactorings(classDiff, refactorings);
      }
      for(UMLClassMoveDiff classDiff : classMoveDiffList) {
    	  inferMethodSignatureRelatedRefactorings(classDiff, refactorings);
      }
      for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
    	  inferMethodSignatureRelatedRefactorings(classDiff, refactorings);
      }
      for(UMLClassRenameDiff classDiff : classRenameDiffList) {
    	  inferMethodSignatureRelatedRefactorings(classDiff, refactorings);
      }
      return filterOutDuplicateRefactorings(refactorings);
   }

   private Map<RenamePattern, Integer> typeRenamePatternMap(Set<Refactoring> refactorings) {
	  Map<RenamePattern, Integer> typeRenamePatternMap = new LinkedHashMap<RenamePattern, Integer>();
	  for(Refactoring ref : refactorings) {
    	  if(ref instanceof ChangeVariableTypeRefactoring) {
    		  ChangeVariableTypeRefactoring refactoring = (ChangeVariableTypeRefactoring)ref;
    		  RenamePattern pattern = new RenamePattern(refactoring.getOriginalVariable().getType().toString(), refactoring.getChangedTypeVariable().getType().toString());
    		  if(typeRenamePatternMap.containsKey(pattern)) {
    			  typeRenamePatternMap.put(pattern, typeRenamePatternMap.get(pattern) + 1);
    		  }
    		  else {
    			  typeRenamePatternMap.put(pattern, 1);
    		  }
    	  }
    	  else if(ref instanceof ChangeAttributeTypeRefactoring) {
    		  ChangeAttributeTypeRefactoring refactoring = (ChangeAttributeTypeRefactoring)ref;
    		  RenamePattern pattern = new RenamePattern(refactoring.getOriginalAttribute().getType().toString(), refactoring.getChangedTypeAttribute().getType().toString());
    		  if(typeRenamePatternMap.containsKey(pattern)) {
    			  typeRenamePatternMap.put(pattern, typeRenamePatternMap.get(pattern) + 1);
    		  }
    		  else {
    			  typeRenamePatternMap.put(pattern, 1);
    		  }
    	  }
    	  else if(ref instanceof ChangeReturnTypeRefactoring) {
    		  ChangeReturnTypeRefactoring refactoring = (ChangeReturnTypeRefactoring)ref;
    		  RenamePattern pattern = new RenamePattern(refactoring.getOriginalType().toString(), refactoring.getChangedType().toString());
    		  if(typeRenamePatternMap.containsKey(pattern)) {
    			  typeRenamePatternMap.put(pattern, typeRenamePatternMap.get(pattern) + 1);
    		  }
    		  else {
    			  typeRenamePatternMap.put(pattern, 1);
    		  }
    	  }
	  }
	  return typeRenamePatternMap;
   }

   private void inferMethodSignatureRelatedRefactorings(UMLClassBaseDiff classDiff, Set<Refactoring> refactorings) {
	  if(classDiff.getOriginalClass().isInterface() && classDiff.getNextClass().isInterface()) {
		  for(UMLOperation removedOperation : classDiff.getRemovedOperations()) {
			  for(UMLOperation addedOperation : classDiff.getAddedOperations()) {
				  List<UMLOperationBodyMapper> mappers = findMappersWithMatchingSignatures(removedOperation, addedOperation);
				  if(!mappers.isEmpty()) {
					  UMLOperationDiff operationSignatureDiff = new UMLOperationDiff(removedOperation, addedOperation);
					  if(operationSignatureDiff.isOperationRenamed()) {
						  RenameOperationRefactoring refactoring = new RenameOperationRefactoring(removedOperation, addedOperation);
						  refactorings.add(refactoring);
					  }
					  Set<Refactoring> signatureRefactorings = operationSignatureDiff.getRefactorings();
					  refactorings.addAll(signatureRefactorings);
					  if(signatureRefactorings.isEmpty()) {
						  inferRefactoringsFromMatchingMappers(mappers, operationSignatureDiff, refactorings);
					  }
				  }
			  }
		  }
	  }
	  else if(classDiff.getOriginalClass().isAbstract() && classDiff.getNextClass().isAbstract()) {
		  for(UMLOperation removedOperation : classDiff.getRemovedOperations()) {
			  for(UMLOperation addedOperation : classDiff.getAddedOperations()) {
				  if(removedOperation.isAbstract() && addedOperation.isAbstract()) {
					  List<UMLOperationBodyMapper> mappers = findMappersWithMatchingSignatures(removedOperation, addedOperation);
					  if(!mappers.isEmpty()) {
						  UMLOperationDiff operationSignatureDiff = new UMLOperationDiff(removedOperation, addedOperation);
						  if(operationSignatureDiff.isOperationRenamed()) {
							  RenameOperationRefactoring refactoring = new RenameOperationRefactoring(removedOperation, addedOperation);
							  refactorings.add(refactoring);
						  }
						  Set<Refactoring> signatureRefactorings = operationSignatureDiff.getRefactorings();
						  refactorings.addAll(signatureRefactorings);
						  if(signatureRefactorings.isEmpty()) {
							  inferRefactoringsFromMatchingMappers(mappers, operationSignatureDiff, refactorings);
						  }
					  }
				  }
			  }
		  }
	  }
   }

   private void inferRefactoringsFromMatchingMappers(List<UMLOperationBodyMapper> mappers, UMLOperationDiff operationSignatureDiff, Set<Refactoring> refactorings) {
	   for(UMLOperationBodyMapper mapper : mappers) {
		   for(Refactoring refactoring : mapper.getRefactoringsAfterPostProcessing()) {
			   if(refactoring instanceof RenameVariableRefactoring) {
				   RenameVariableRefactoring rename = (RenameVariableRefactoring)refactoring;
				   UMLParameter matchingRemovedParameter = null;
				   for(UMLParameter parameter : operationSignatureDiff.getRemovedParameters()) {
					   if(parameter.getName().equals(rename.getOriginalVariable().getVariableName()) &&
							   parameter.getType().equals(rename.getOriginalVariable().getType())) {
						   matchingRemovedParameter = parameter;
						   break;
					   }
				   }
				   UMLParameter matchingAddedParameter = null;
				   for(UMLParameter parameter : operationSignatureDiff.getAddedParameters()) {
					   if(parameter.getName().equals(rename.getRenamedVariable().getVariableName()) &&
							   parameter.getType().equals(rename.getRenamedVariable().getType())) {
						   matchingAddedParameter = parameter;
						   break;
					   }
				   }
				   if(matchingRemovedParameter != null && matchingAddedParameter != null) {
					   RenameVariableRefactoring newRename = new RenameVariableRefactoring(matchingRemovedParameter.getVariableDeclaration(), matchingAddedParameter.getVariableDeclaration(),
							   operationSignatureDiff.getRemovedOperation(), operationSignatureDiff.getAddedOperation(), new LinkedHashSet<AbstractCodeMapping>());
					   refactorings.add(newRename);
				   }
			   }
			   else if(refactoring instanceof ChangeVariableTypeRefactoring) {
				   ChangeVariableTypeRefactoring changeType = (ChangeVariableTypeRefactoring)refactoring;
				   UMLParameter matchingRemovedParameter = null;
				   for(UMLParameter parameter : operationSignatureDiff.getRemovedParameters()) {
					   if(parameter.getName().equals(changeType.getOriginalVariable().getVariableName()) &&
							   parameter.getType().equals(changeType.getOriginalVariable().getType())) {
						   matchingRemovedParameter = parameter;
						   break;
					   }
				   }
				   UMLParameter matchingAddedParameter = null;
				   for(UMLParameter parameter : operationSignatureDiff.getAddedParameters()) {
					   if(parameter.getName().equals(changeType.getChangedTypeVariable().getVariableName()) &&
							   parameter.getType().equals(changeType.getChangedTypeVariable().getType())) {
						   matchingAddedParameter = parameter;
						   break;
					   }
				   }
				   if(matchingRemovedParameter != null && matchingAddedParameter != null) {
					   ChangeVariableTypeRefactoring newChangeType = new ChangeVariableTypeRefactoring(matchingRemovedParameter.getVariableDeclaration(), matchingAddedParameter.getVariableDeclaration(),
							   operationSignatureDiff.getRemovedOperation(), operationSignatureDiff.getAddedOperation(), new LinkedHashSet<AbstractCodeMapping>());
					   refactorings.add(newChangeType);
				   }
			   }
		   }
	   }
   }

   private List<UMLOperationBodyMapper> findMappersWithMatchingSignatures(UMLOperation operation1, UMLOperation operation2) {
	   List<UMLOperationBodyMapper> mappers = new ArrayList<UMLOperationBodyMapper>();
	   for(UMLClassDiff classDiff : commonClassDiffList) {
		   UMLOperationBodyMapper mapper = classDiff.findMapperWithMatchingSignatures(operation1, operation2);
		   if(mapper != null) {
			   mappers.add(mapper);
		   }
	   }
	   for(UMLClassMoveDiff classDiff : classMoveDiffList) {
		   UMLOperationBodyMapper mapper = classDiff.findMapperWithMatchingSignatures(operation1, operation2);
		   if(mapper != null) {
			   mappers.add(mapper);
		   }
	   }
	   for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
		   UMLOperationBodyMapper mapper = classDiff.findMapperWithMatchingSignatures(operation1, operation2);
		   if(mapper != null) {
			   mappers.add(mapper);
		   }
	   }
	   for(UMLClassRenameDiff classDiff : classRenameDiffList) {
		   UMLOperationBodyMapper mapper = classDiff.findMapperWithMatchingSignatures(operation1, operation2);
		   if(mapper != null) {
			   mappers.add(mapper);
		   }
	   }
	   return mappers;
   }

   public List<UMLOperationBodyMapper> findMappersWithMatchingSignature2(UMLOperation operation2) {
	   List<UMLOperationBodyMapper> mappers = new ArrayList<UMLOperationBodyMapper>();
	   for(UMLClassDiff classDiff : commonClassDiffList) {
		   UMLOperationBodyMapper mapper = classDiff.findMapperWithMatchingSignature2(operation2);
		   if(mapper != null) {
			   mappers.add(mapper);
		   }
	   }
	   for(UMLClassMoveDiff classDiff : classMoveDiffList) {
		   UMLOperationBodyMapper mapper = classDiff.findMapperWithMatchingSignature2(operation2);
		   if(mapper != null) {
			   mappers.add(mapper);
		   }
	   }
	   for(UMLClassMoveDiff classDiff : innerClassMoveDiffList) {
		   UMLOperationBodyMapper mapper = classDiff.findMapperWithMatchingSignature2(operation2);
		   if(mapper != null) {
			   mappers.add(mapper);
		   }
	   }
	   for(UMLClassRenameDiff classDiff : classRenameDiffList) {
		   UMLOperationBodyMapper mapper = classDiff.findMapperWithMatchingSignature2(operation2);
		   if(mapper != null) {
			   mappers.add(mapper);
		   }
	   }
	   return mappers;
   }

   private void extractMergePatterns(UMLClassBaseDiff classDiff, Map<MergeVariableReplacement, Set<CandidateMergeVariableRefactoring>> mergeMap) {
	   for(CandidateMergeVariableRefactoring candidate : classDiff.getCandidateAttributeMerges()) {
			Set<String> before = new LinkedHashSet<String>();
			for(String mergedVariable : candidate.getMergedVariables()) {
				before.add(PrefixSuffixUtils.normalize(mergedVariable));
			}
			String after = PrefixSuffixUtils.normalize(candidate.getNewVariable());
			MergeVariableReplacement merge = new MergeVariableReplacement(before, after);
			if(mergeMap.containsKey(merge)) {
				mergeMap.get(merge).add(candidate);
			}
			else {
				Set<CandidateMergeVariableRefactoring> set = new LinkedHashSet<CandidateMergeVariableRefactoring>();
				set.add(candidate);
				mergeMap.put(merge, set);
			}
		}
   }

   private void extractRenamePatterns(UMLClassBaseDiff classDiff, Map<Replacement, Set<CandidateAttributeRefactoring>> map) {
	  for(CandidateAttributeRefactoring candidate : classDiff.getCandidateAttributeRenames()) {
		 String before = PrefixSuffixUtils.normalize(candidate.getOriginalVariableName());
		 String after = PrefixSuffixUtils.normalize(candidate.getRenamedVariableName());
		 if(before.contains(".") && after.contains(".")) {
				String prefix1 = before.substring(0, before.lastIndexOf(".") + 1);
				String prefix2 = after.substring(0, after.lastIndexOf(".") + 1);
				if(prefix1.equals(prefix2)) {
					before = before.substring(prefix1.length(), before.length());
					after = after.substring(prefix2.length(), after.length());
				}
			}
		 Replacement renamePattern = new Replacement(before, after, ReplacementType.VARIABLE_NAME);
		 if(map.containsKey(renamePattern)) {
			 map.get(renamePattern).add(candidate);
		 }
		 else {
			 Set<CandidateAttributeRefactoring> set = new LinkedHashSet<CandidateAttributeRefactoring>();
			 set.add(candidate);
			 map.put(renamePattern, set);
		 }
	  }
   }

   private void checkForMovedAndInlinedOperations(List<UMLOperationBodyMapper> mappers, List<UMLOperation> removedOperations) throws RefactoringMinerTimedOutException {
	   for(Iterator<UMLOperation> removedOperationIterator = removedOperations.iterator(); removedOperationIterator.hasNext();) {
		   UMLOperation removedOperation = removedOperationIterator.next();
		   for(UMLOperationBodyMapper mapper : mappers) {
			   if(!mapper.getNonMappedLeavesT2().isEmpty() || !mapper.getNonMappedInnerNodesT2().isEmpty() || !mapper.getReplacementsInvolvingMethodInvocation().isEmpty()) {
				   List<OperationInvocation> operationInvocations = mapper.getOperation1().getAllOperationInvocations();
				   List<OperationInvocation> removedOperationInvocations = new ArrayList<OperationInvocation>();
				   for(OperationInvocation invocation : operationInvocations) {
					   if(invocation.matchesOperation(removedOperation, mapper.getOperation1().variableTypeMap(), this)) {
						   removedOperationInvocations.add(invocation);
					   }
				   }
				   if(removedOperationInvocations.size() > 0 && !invocationMatchesWithAddedOperation(removedOperationInvocations.get(0), mapper.getOperation1().variableTypeMap(), mapper.getOperation2().getAllOperationInvocations())) {
						OperationInvocation removedOperationInvocation = removedOperationInvocations.get(0);
						List<String> arguments = removedOperationInvocation.getArguments();
						List<String> parameters = removedOperation.getParameterNameList();
						Map<String, String> parameterToArgumentMap = new LinkedHashMap<String, String>();
						//special handling for methods with varargs parameter for which no argument is passed in the matching invocation
						int size = Math.min(arguments.size(), parameters.size());
						for(int i=0; i<size; i++) {
							parameterToArgumentMap.put(parameters.get(i), arguments.get(i));
						}
						UMLOperationBodyMapper operationBodyMapper = new UMLOperationBodyMapper(removedOperation, mapper, parameterToArgumentMap, getUMLClassDiff(removedOperation.getClassName()));
						if(moveAndInlineMatchCondition(operationBodyMapper, mapper)) {
							InlineOperationRefactoring inlineOperationRefactoring =	new InlineOperationRefactoring(operationBodyMapper, mapper.getOperation1(), removedOperationInvocations);
							refactorings.add(inlineOperationRefactoring);
							deleteRemovedOperation(removedOperation);
						}
				   }
			   }
		   }
	   }
   }

	private boolean moveAndInlineMatchCondition(UMLOperationBodyMapper operationBodyMapper, UMLOperationBodyMapper parentMapper) {
		List<AbstractCodeMapping> mappingList = new ArrayList<AbstractCodeMapping>(operationBodyMapper.getMappings());
		if((operationBodyMapper.getOperation1().isGetter() || operationBodyMapper.getOperation1().isDelegate() != null) && mappingList.size() == 1) {
			List<AbstractCodeMapping> parentMappingList = new ArrayList<AbstractCodeMapping>(parentMapper.getMappings());
			for(AbstractCodeMapping mapping : parentMappingList) {
				if(mapping.getFragment2().equals(mappingList.get(0).getFragment2())) {
					return false;
				}
				if(mapping instanceof CompositeStatementObjectMapping) {
					CompositeStatementObjectMapping compositeMapping = (CompositeStatementObjectMapping)mapping;
					CompositeStatementObject fragment2 = (CompositeStatementObject)compositeMapping.getFragment2();
					for(AbstractExpression expression : fragment2.getExpressions()) {
						if(expression.equals(mappingList.get(0).getFragment2())) {
							return false;
						}
					}
				}
			}
		}
		int delegateStatements = 0;
		for(StatementObject statement : operationBodyMapper.getNonMappedLeavesT1()) {
			OperationInvocation invocation = statement.invocationCoveringEntireFragment();
			if(invocation != null && invocation.matchesOperation(operationBodyMapper.getOperation1())) {
				delegateStatements++;
			}
		}
		int mappings = operationBodyMapper.mappingsWithoutBlocks();
		int nonMappedElementsT1 = operationBodyMapper.nonMappedElementsT1()-delegateStatements;
		List<AbstractCodeMapping> exactMatchList = operationBodyMapper.getExactMatches();
		int exactMatches = exactMatchList.size();
		return mappings > 0 && (mappings > nonMappedElementsT1 ||
				(exactMatches == 1 && !exactMatchList.get(0).getFragment1().throwsNewException() && nonMappedElementsT1-exactMatches < 10) ||
				(exactMatches > 1 && nonMappedElementsT1-exactMatches < 20));
	}

	private boolean invocationMatchesWithAddedOperation(OperationInvocation removedOperationInvocation, Map<String, UMLType> variableTypeMap, List<OperationInvocation> operationInvocationsInNewMethod) {
		if(operationInvocationsInNewMethod.contains(removedOperationInvocation)) {
			for(UMLOperation addedOperation : getAddedOperationsInCommonClasses()) {
				if(removedOperationInvocation.matchesOperation(addedOperation, variableTypeMap, this)) {
					return true;
				}
			}
		}
		return false;
	}

   private void checkForExtractedAndMovedOperations(List<UMLOperationBodyMapper> mappers, List<UMLOperation> addedOperations) throws RefactoringMinerTimedOutException {
      for(Iterator<UMLOperation> addedOperationIterator = addedOperations.iterator(); addedOperationIterator.hasNext();) {
    	  UMLOperation addedOperation = addedOperationIterator.next();
    	  for(UMLOperationBodyMapper mapper : mappers) {
    		  if((mapper.nonMappedElementsT1() > 0 || !mapper.getReplacementsInvolvingMethodInvocation().isEmpty()) && !mapper.containsExtractOperationRefactoring(addedOperation)) {
               List<OperationInvocation> operationInvocations = ExtractOperationDetection.getInvocationsInSourceOperationAfterExtraction(mapper);
               List<OperationInvocation> addedOperationInvocations = new ArrayList<OperationInvocation>();
               for(OperationInvocation invocation : operationInvocations) {
                  if(invocation.matchesOperation(addedOperation, mapper.getOperation2().variableTypeMap(), this)) {
                     addedOperationInvocations.add(invocation);
                  }
               }
               if(addedOperationInvocations.size() > 0) {
            	  OperationInvocation addedOperationInvocation = addedOperationInvocations.get(0);
            	  List<String> arguments = addedOperationInvocation.getArguments();
            	  List<String> parameters = addedOperation.getParameterNameList();
            	  Map<String, String> parameterToArgumentMap2 = new LinkedHashMap<String, String>();
            	  //special handling for methods with varargs parameter for which no argument is passed in the matching invocation
				  int size = Math.min(arguments.size(), parameters.size());
            	  for(int i=0; i<size; i++) {
            		  parameterToArgumentMap2.put(parameters.get(i), arguments.get(i));
            	  }
            	  String className = mapper.getOperation2().getClassName();
            	  List<UMLAttribute> attributes = new ArrayList<UMLAttribute>();
            	  if(className.contains(".") && isNumeric(className.substring(className.lastIndexOf(".")+1, className.length()))) {
            		  //add enclosing class fields + anonymous class fields
            		  UMLClassBaseDiff umlClassDiff = getUMLClassDiff(className.substring(0, className.lastIndexOf(".")));
            		  attributes.addAll(umlClassDiff.originalClassAttributesOfType(addedOperation.getClassName()));
            		  for(UMLAnonymousClass anonymous : umlClassDiff.getOriginalClass().getAnonymousClassList()) {
            			  if(anonymous.getName().equals(className)) {
            				  attributes.addAll(anonymous.attributesOfType(addedOperation.getClassName()));
            				  break;
            			  }
            		  }
            	  }
            	  else {
            		  UMLClassBaseDiff umlClassDiff = getUMLClassDiff(className);
            		  if(umlClassDiff == null) {
            			  for(UMLClassDiff classDiff : commonClassDiffList) {
            				  for(UMLAnonymousClass anonymousClass : classDiff.getAddedAnonymousClasses()) {
            					  if(className.equals(anonymousClass.getCodePath())) {
            						  umlClassDiff = classDiff;
            						  attributes.addAll(anonymousClass.attributesOfType(addedOperation.getClassName()));
            						  break;
            					  }
            				  }
            				  if(umlClassDiff != null) {
            					  break;
            				  }
            			  }
            		  }
            		  attributes.addAll(umlClassDiff.originalClassAttributesOfType(addedOperation.getClassName()));
            	  }
            	  Map<String, String> parameterToArgumentMap1 = new LinkedHashMap<String, String>();
            	  for(UMLAttribute attribute : attributes) {
            		  parameterToArgumentMap1.put(attribute.getName() + ".", "");
            		  parameterToArgumentMap2.put("this.", "");
            	  }
            	  if(addedOperationInvocation.getExpression() != null) {
            		  parameterToArgumentMap1.put(addedOperationInvocation.getExpression() + ".", "");
            		  parameterToArgumentMap2.put("this.", "");
            	  }
                  UMLOperationBodyMapper operationBodyMapper = new UMLOperationBodyMapper(mapper, addedOperation, parameterToArgumentMap1, parameterToArgumentMap2, getUMLClassDiff(addedOperation.getClassName()));
                  if(!anotherAddedMethodExistsWithBetterMatchingInvocationExpression(addedOperationInvocation, addedOperation, addedOperations) &&
                		  !conflictingExpression(addedOperationInvocation, addedOperation, mapper.getOperation2().variableTypeMap()) &&
                		  extractAndMoveMatchCondition(operationBodyMapper, mapper)) {
                	  if(className.equals(addedOperation.getClassName())) {
                		  //extract inside moved or renamed class
                		  ExtractOperationRefactoring extractOperationRefactoring =
   	                           new ExtractOperationRefactoring(operationBodyMapper, mapper.getOperation2(), addedOperationInvocations);
   	                      refactorings.add(extractOperationRefactoring);
   	                      deleteAddedOperation(addedOperation);
                	  }
                	  else if(isSubclassOf(className, addedOperation.getClassName())) {
                		  //extract and pull up method
                		  ExtractOperationRefactoring extractOperationRefactoring =
   	                           new ExtractOperationRefactoring(operationBodyMapper, mapper.getOperation2(), addedOperationInvocations);
   	                      refactorings.add(extractOperationRefactoring);
   	                      deleteAddedOperation(addedOperation);
                	  }
                	  else if(isSubclassOf(addedOperation.getClassName(), className)) {
                		  //extract and push down method
                		  ExtractOperationRefactoring extractOperationRefactoring =
   	                           new ExtractOperationRefactoring(operationBodyMapper, mapper.getOperation2(), addedOperationInvocations);
   	                      refactorings.add(extractOperationRefactoring);
   	                      deleteAddedOperation(addedOperation);
                	  }
                	  else if(addedOperation.getClassName().startsWith(className + ".")) {
                		  //extract and move to inner class
                		  ExtractOperationRefactoring extractOperationRefactoring =
      	                       new ExtractOperationRefactoring(operationBodyMapper, mapper.getOperation2(), addedOperationInvocations);
      	                  refactorings.add(extractOperationRefactoring);
      	                  deleteAddedOperation(addedOperation);
                	  }
                	  else if(className.startsWith(addedOperation.getClassName() + ".")) {
                		  //extract and move to outer class
                		  ExtractOperationRefactoring extractOperationRefactoring =
      	                       new ExtractOperationRefactoring(operationBodyMapper, mapper.getOperation2(), addedOperationInvocations);
      	                  refactorings.add(extractOperationRefactoring);
      	                  deleteAddedOperation(addedOperation);
                	  }
                	  else if(sourceClassImportsTargetClass(className, addedOperation.getClassName()) ||
                			  sourceClassImportsSuperclassOfTargetClass(className, addedOperation.getClassName()) ||
                			  targetClassImportsSourceClass(className, addedOperation.getClassName())) {
                		  //extract and move
	                      ExtractOperationRefactoring extractOperationRefactoring =
	                           new ExtractOperationRefactoring(operationBodyMapper, mapper.getOperation2(), addedOperationInvocations);
	                      refactorings.add(extractOperationRefactoring);
	                      deleteAddedOperation(addedOperation);
                	  }
                  }
               }
            }
         }
      }
   }

   private boolean conflictingExpression(OperationInvocation invocation, UMLOperation addedOperation, Map<String, UMLType> variableTypeMap) {
	   String expression = invocation.getExpression();
	   if(expression != null && variableTypeMap.containsKey(expression)) {
		   UMLType type = variableTypeMap.get(expression);
		   UMLClassBaseDiff classDiff = getUMLClassDiff(addedOperation.getClassName());
		   boolean superclassRelationship = false;
		   if(classDiff != null && classDiff.getNewSuperclass() != null &&
				   classDiff.getNewSuperclass().equals(type)) {
			   superclassRelationship = true;
		   }
		   if(!addedOperation.getNonQualifiedClassName().equals(type.getClassType()) && !superclassRelationship) {
			   return true;
		   }
	   }
	   return false;
   }

   private boolean anotherAddedMethodExistsWithBetterMatchingInvocationExpression(OperationInvocation invocation, UMLOperation addedOperation, List<UMLOperation> addedOperations) {
	   String expression = invocation.getExpression();
	   if(expression != null) {
		   int originalDistance = StringDistance.editDistance(expression, addedOperation.getNonQualifiedClassName());
		   for(UMLOperation operation : addedOperations) {
			   UMLClassBaseDiff classDiff = getUMLClassDiff(operation.getClassName());
			   boolean isInterface = classDiff != null ? classDiff.nextClass.isInterface() : false;
			   if(!operation.equals(addedOperation) && addedOperation.equalSignature(operation) && !operation.isAbstract() && !isInterface) {
				   int newDistance = StringDistance.editDistance(expression, operation.getNonQualifiedClassName());
				   if(newDistance < originalDistance) {
					   return true;
				   }
			   }
		   }
	   }
	   return false;
   }

   private boolean extractAndMoveMatchCondition(UMLOperationBodyMapper operationBodyMapper, UMLOperationBodyMapper parentMapper) {
	   List<AbstractCodeMapping> mappingList = new ArrayList<AbstractCodeMapping>(operationBodyMapper.getMappings());
	   if(operationBodyMapper.getOperation2().isGetter() && mappingList.size() == 1) {
		   List<AbstractCodeMapping> parentMappingList = new ArrayList<AbstractCodeMapping>(parentMapper.getMappings());
		   for(AbstractCodeMapping mapping : parentMappingList) {
			   if(mapping.getFragment1().equals(mappingList.get(0).getFragment1())) {
				   return false;
			   }
			   if(mapping instanceof CompositeStatementObjectMapping) {
				   CompositeStatementObjectMapping compositeMapping = (CompositeStatementObjectMapping)mapping;
				   CompositeStatementObject fragment1 = (CompositeStatementObject)compositeMapping.getFragment1();
				   for(AbstractExpression expression : fragment1.getExpressions()) {
					   if(expression.equals(mappingList.get(0).getFragment1())) {
						   return false;
					   }
				   }
			   }
		   }
		   if(operationBodyMapper.getOperation1().isGetter()) {
			   if(mappingList.get(0).getFragment1().getString().startsWith("return this;") ||
					   mappingList.get(0).getFragment2().getString().startsWith("return this;")) {
				   return false;
			   }
		   }
	   }
	   int mappings = operationBodyMapper.mappingsWithoutBlocks();
	   int nonMappedElementsT1 = operationBodyMapper.nonMappedElementsT1();
	   int nonMappedElementsT2 = operationBodyMapper.nonMappedElementsT2();
	   List<AbstractCodeMapping> exactMatchList = operationBodyMapper.getExactMatches();
	   int exactMatches = exactMatchList.size();
	   return mappings > 0 && (mappings > nonMappedElementsT2 || (mappings > 1 && mappings >= nonMappedElementsT2) ||
			   (exactMatches == mappings && nonMappedElementsT1 == 0) ||
			   (exactMatches == 1 && !exactMatchList.get(0).getFragment1().throwsNewException() && nonMappedElementsT2-exactMatches <= 10) ||
			   (exactMatches > 1 && nonMappedElementsT2-exactMatches < 20) ||
			   (mappings == 1 && mappings > operationBodyMapper.nonMappedLeafElementsT2()));
   }

   private void checkForOperationMovesIncludingRemovedClasses() throws RefactoringMinerTimedOutException {
      List<UMLOperation> addedOperations = getAddedAndExtractedOperationsInCommonClasses();
      /*for(UMLClass addedClass : addedClasses) {
    	  addedOperations.addAll(addedClass.getOperations());
      }*/
      List<UMLOperation> removedOperations = getRemovedOperationsInCommonClasses();
      for(UMLClass removedClass : removedClasses) {
    	  removedOperations.addAll(removedClass.getOperations());
      }
      if(removedOperations.size() <= MAXIMUM_NUMBER_OF_COMPARED_METHODS || addedOperations.size() <= MAXIMUM_NUMBER_OF_COMPARED_METHODS) {
    	  checkForOperationMoves(addedOperations, removedOperations);
      }
   }

   private void checkForOperationMovesIncludingAddedClasses() throws RefactoringMinerTimedOutException {
      List<UMLOperation> addedOperations = getAddedOperationsInCommonClasses();
      for(UMLClass addedClass : addedClasses) {
    	  addedOperations.addAll(addedClass.getOperations());
      }
      List<UMLOperation> removedOperations = getRemovedOperationsInCommonClasses();
      /*for(UMLClass removedClass : removedClasses) {
    	  removedOperations.addAll(removedClass.getOperations());
      }*/
      if(removedOperations.size() <= MAXIMUM_NUMBER_OF_COMPARED_METHODS || addedOperations.size() <= MAXIMUM_NUMBER_OF_COMPARED_METHODS) {
    	  checkForOperationMoves(addedOperations, removedOperations);
      }
   }

   private void checkForOperationMovesBetweenCommonClasses() throws RefactoringMinerTimedOutException {
      List<UMLOperation> addedOperations = getAddedAndExtractedOperationsInCommonClasses();
      List<UMLOperation> removedOperations = getRemovedOperationsInCommonMovedRenamedClasses();
      if(removedOperations.size() <= MAXIMUM_NUMBER_OF_COMPARED_METHODS || addedOperations.size() <= MAXIMUM_NUMBER_OF_COMPARED_METHODS) {
    	  checkForOperationMoves(addedOperations, removedOperations);
      }
   }

   private void checkForOperationMovesBetweenRemovedAndAddedClasses() throws RefactoringMinerTimedOutException {
	   Set<UMLType> interfacesImplementedByAddedClasses = new LinkedHashSet<UMLType>();
	   for(UMLClass addedClass : addedClasses) {
		   interfacesImplementedByAddedClasses.addAll(addedClass.getImplementedInterfaces());
	   }
	   Set<UMLType> interfacesImplementedByRemovedClasses = new LinkedHashSet<UMLType>();
	   for(UMLClass removedClass : removedClasses) {
		   interfacesImplementedByRemovedClasses.addAll(removedClass.getImplementedInterfaces());
	   }
	   Set<UMLType> interfaceIntersection = new LinkedHashSet<UMLType>(interfacesImplementedByAddedClasses);
	   interfaceIntersection.retainAll(interfacesImplementedByRemovedClasses);
	   List<UMLOperation> addedOperations = new ArrayList<UMLOperation>();
	   for(UMLClass addedClass : addedClasses) {
		   if(!addedClass.implementsInterface(interfaceIntersection) && !outerClassMovedOrRenamed(addedClass)) {
			   addedOperations.addAll(addedClass.getOperations());
		   }
	   }
	   List<UMLOperation> removedOperations = new ArrayList<UMLOperation>();
	   for(UMLClass removedClass : removedClasses) {
		   if(!removedClass.implementsInterface(interfaceIntersection) && !outerClassMovedOrRenamed(removedClass)) {
			   removedOperations.addAll(removedClass.getOperations());
		   }
	   }
	   if(removedOperations.size() <= MAXIMUM_NUMBER_OF_COMPARED_METHODS || addedOperations.size() <= MAXIMUM_NUMBER_OF_COMPARED_METHODS) {
		   checkForOperationMoves(addedOperations, removedOperations);
	   }
   }

   private boolean outerClassMovedOrRenamed(UMLClass umlClass) {
	   if(!umlClass.isTopLevel()) {
		   for(UMLClassMoveDiff diff : classMoveDiffList) {
			   if(diff.getOriginalClass().getName().equals(umlClass.getPackageName()) ||
					   diff.getMovedClass().getName().equals(umlClass.getPackageName())) {
				   return true;
			   }
		   }
		   for(UMLClassRenameDiff diff : classRenameDiffList) {
			   if(diff.getOriginalClass().getName().equals(umlClass.getPackageName()) ||
					   diff.getRenamedClass().getName().equals(umlClass.getPackageName())) {
				   return true;
			   }
		   }
	   }
	   return false;
   }

   private void checkForOperationMoves(List<UMLOperation> addedOperations, List<UMLOperation> removedOperations) throws RefactoringMinerTimedOutException {
	   if(addedOperations.size() <= removedOperations.size()) {
	      for(Iterator<UMLOperation> addedOperationIterator = addedOperations.iterator(); addedOperationIterator.hasNext();) {
	         UMLOperation addedOperation = addedOperationIterator.next();
	         TreeMap<Integer, List<UMLOperationBodyMapper>> operationBodyMapperMap = new TreeMap<Integer, List<UMLOperationBodyMapper>>();
	         for(Iterator<UMLOperation> removedOperationIterator = removedOperations.iterator(); removedOperationIterator.hasNext();) {
	            UMLOperation removedOperation = removedOperationIterator.next();
	            
	            UMLOperationBodyMapper operationBodyMapper = new UMLOperationBodyMapper(removedOperation, addedOperation, null);
	            int mappings = operationBodyMapper.mappingsWithoutBlocks();
	            if(mappings > 0 && mappedElementsMoreThanNonMappedT1AndT2(mappings, operationBodyMapper)) {
	               int exactMatches = operationBodyMapper.exactMatches();
	               if(operationBodyMapperMap.containsKey(exactMatches)) {
	                  List<UMLOperationBodyMapper> mapperList = operationBodyMapperMap.get(exactMatches);
	                  mapperList.add(operationBodyMapper);
	               }
	               else {
	                  List<UMLOperationBodyMapper> mapperList = new ArrayList<UMLOperationBodyMapper>();
	                  mapperList.add(operationBodyMapper);
	                  operationBodyMapperMap.put(exactMatches, mapperList);
	               }
	            }
	         }
	         if(!operationBodyMapperMap.isEmpty()) {
	            List<UMLOperationBodyMapper> firstMappers = firstMappers(operationBodyMapperMap);
	            Collections.sort(firstMappers, new UMLOperationBodyMapperComparator());
	            addedOperationIterator.remove();
	            boolean sameSourceAndTargetClass = sameSourceAndTargetClass(firstMappers);
	            if(sameSourceAndTargetClass) {
	            	TreeSet<UMLOperationBodyMapper> set = null;
	            	if(allRenamedOperations(firstMappers)) {
	            		set = new TreeSet<UMLOperationBodyMapper>();
	            	}
	            	else {
	            		set = new TreeSet<UMLOperationBodyMapper>(new UMLOperationBodyMapperComparator());
	            	}
	            	set.addAll(firstMappers);
	            	UMLOperationBodyMapper bestMapper = set.first();
	            	firstMappers.clear();
	            	firstMappers.add(bestMapper);
	            }
	            for(UMLOperationBodyMapper firstMapper : firstMappers) {
	               UMLOperation removedOperation = firstMapper.getOperation1();
	               if(sameSourceAndTargetClass) {
	                  removedOperations.remove(removedOperation);
	               }
	
	               Refactoring refactoring = null;
	               if(removedOperation.getClassName().equals(addedOperation.getClassName())) {
	            	  if (addedOperation.equalParameters(removedOperation)) {
	            		  //refactoring = new RenameOperationRefactoring(removedOperation, addedOperation);
	            	  } else {
	            		  // Methods in the same class with similar body but different signature
	            	  }
	               }
	               else if(removedOperation.isConstructor() == addedOperation.isConstructor() &&
	            		   isSubclassOf(removedOperation.getClassName(), addedOperation.getClassName()) && addedOperation.compatibleSignature(removedOperation)) {
	                  refactoring = new PullUpOperationRefactoring(firstMapper);
	               }
	               else if(removedOperation.isConstructor() == addedOperation.isConstructor() &&
	            		   isSubclassOf(addedOperation.getClassName(), removedOperation.getClassName()) && addedOperation.compatibleSignature(removedOperation)) {
	                  refactoring = new PushDownOperationRefactoring(firstMapper);
	               }
	               else if(removedOperation.isConstructor() == addedOperation.isConstructor() &&
	            		   movedMethodSignature(removedOperation, addedOperation) && !refactoringListContainsAnotherMoveRefactoringWithTheSameOperations(removedOperation, addedOperation)) {
	                  refactoring = new MoveOperationRefactoring(firstMapper);
	               }
	               else if(removedOperation.isConstructor() == addedOperation.isConstructor() &&
	            		   movedAndRenamedMethodSignature(removedOperation, addedOperation, firstMapper) && !refactoringListContainsAnotherMoveRefactoringWithTheSameOperations(removedOperation, addedOperation)) {
	                  refactoring = new MoveOperationRefactoring(firstMapper);
	               }
	               if(refactoring != null) {
	                  deleteRemovedOperation(removedOperation);
	                  deleteAddedOperation(addedOperation);
	                  UMLOperationDiff operationSignatureDiff = new UMLOperationDiff(removedOperation, addedOperation, firstMapper.getMappings());
	                  refactorings.addAll(operationSignatureDiff.getRefactorings());
	                  refactorings.add(refactoring);
	                  UMLClass addedClass = getAddedClass(addedOperation.getClassName());
	                  if(addedClass != null) {
	                	  checkForExtractedOperationsWithinMovedMethod(firstMapper, addedClass);
	                  }
	               }
	            }
	         }
	      }
      }
      else {
    	  for(Iterator<UMLOperation> removedOperationIterator = removedOperations.iterator(); removedOperationIterator.hasNext();) {
	         UMLOperation removedOperation = removedOperationIterator.next();
	         TreeMap<Integer, List<UMLOperationBodyMapper>> operationBodyMapperMap = new TreeMap<Integer, List<UMLOperationBodyMapper>>();
	         for(Iterator<UMLOperation> addedOperationIterator = addedOperations.iterator(); addedOperationIterator.hasNext();) {
	            UMLOperation addedOperation = addedOperationIterator.next();
	            
	            UMLOperationBodyMapper operationBodyMapper = new UMLOperationBodyMapper(removedOperation, addedOperation, null);
	            int mappings = operationBodyMapper.mappingsWithoutBlocks();
	            if(mappings > 0 && mappedElementsMoreThanNonMappedT1AndT2(mappings, operationBodyMapper)) {
	               int exactMatches = operationBodyMapper.exactMatches();
	               if(operationBodyMapperMap.containsKey(exactMatches)) {
	                  List<UMLOperationBodyMapper> mapperList = operationBodyMapperMap.get(exactMatches);
	                  mapperList.add(operationBodyMapper);
	               }
	               else {
	                  List<UMLOperationBodyMapper> mapperList = new ArrayList<UMLOperationBodyMapper>();
	                  mapperList.add(operationBodyMapper);
	                  operationBodyMapperMap.put(exactMatches, mapperList);
	               }
	            }
	         }
	         if(!operationBodyMapperMap.isEmpty()) {
	            List<UMLOperationBodyMapper> firstMappers = firstMappers(operationBodyMapperMap);
	            Collections.sort(firstMappers, new UMLOperationBodyMapperComparator());
	            removedOperationIterator.remove();
	            boolean sameSourceAndTargetClass = sameSourceAndTargetClass(firstMappers);
	            if(sameSourceAndTargetClass) {
	            	TreeSet<UMLOperationBodyMapper> set = null;
	            	if(allRenamedOperations(firstMappers)) {
	            		set = new TreeSet<UMLOperationBodyMapper>();
	            	}
	            	else {
	            		set = new TreeSet<UMLOperationBodyMapper>(new UMLOperationBodyMapperComparator());
	            	}
	            	set.addAll(firstMappers);
	            	UMLOperationBodyMapper bestMapper = set.first();
	            	firstMappers.clear();
	            	firstMappers.add(bestMapper);
	            }
	            for(UMLOperationBodyMapper firstMapper : firstMappers) {
	               UMLOperation addedOperation = firstMapper.getOperation2();
	               if(sameSourceAndTargetClass) {
	                  addedOperations.remove(addedOperation);
	               }

	               Refactoring refactoring = null;
	               if(removedOperation.getClassName().equals(addedOperation.getClassName())) {
	            	  if (addedOperation.equalParameters(removedOperation)) {
	            		  //refactoring = new RenameOperationRefactoring(removedOperation, addedOperation);
	            	  } else {
	            		  // Methods in the same class with similar body but different signature
	            	  }
	               }
	               else if(removedOperation.isConstructor() == addedOperation.isConstructor() &&
	            		   isSubclassOf(removedOperation.getClassName(), addedOperation.getClassName()) && addedOperation.compatibleSignature(removedOperation)) {
	                  refactoring = new PullUpOperationRefactoring(firstMapper);
	               }
	               else if(removedOperation.isConstructor() == addedOperation.isConstructor() &&
	            		   isSubclassOf(addedOperation.getClassName(), removedOperation.getClassName()) && addedOperation.compatibleSignature(removedOperation)) {
	                  refactoring = new PushDownOperationRefactoring(firstMapper);
	               }
	               else if(removedOperation.isConstructor() == addedOperation.isConstructor() &&
	            		   movedMethodSignature(removedOperation, addedOperation) && !refactoringListContainsAnotherMoveRefactoringWithTheSameOperations(removedOperation, addedOperation)) {
	                  refactoring = new MoveOperationRefactoring(firstMapper);
	               }
	               else if(removedOperation.isConstructor() == addedOperation.isConstructor() &&
	            		   movedAndRenamedMethodSignature(removedOperation, addedOperation, firstMapper) && !refactoringListContainsAnotherMoveRefactoringWithTheSameOperations(removedOperation, addedOperation)) {
	                  refactoring = new MoveOperationRefactoring(firstMapper);
	               }
	               if(refactoring != null) {
	                  deleteRemovedOperation(removedOperation);
	                  deleteAddedOperation(addedOperation);
	                  UMLOperationDiff operationSignatureDiff = new UMLOperationDiff(removedOperation, addedOperation, firstMapper.getMappings());
	                  refactorings.addAll(operationSignatureDiff.getRefactorings());
	                  refactorings.add(refactoring);
	               }
	            }
	         }
	      }
      }
   }

	private List<UMLOperationBodyMapper> firstMappers(TreeMap<Integer, List<UMLOperationBodyMapper>> operationBodyMapperMap) {
		List<UMLOperationBodyMapper> firstMappers = new ArrayList<UMLOperationBodyMapper>(operationBodyMapperMap.get(operationBodyMapperMap.lastKey()));
		List<UMLOperationBodyMapper> extraMappers = operationBodyMapperMap.get(0);
		if(extraMappers != null && operationBodyMapperMap.lastKey() != 0) {
			for(UMLOperationBodyMapper extraMapper : extraMappers) {
				UMLOperation operation1 = extraMapper.getOperation1();
				UMLOperation operation2 = extraMapper.getOperation2();
				if(operation1.equalSignature(operation2)) {
					List<AbstractCodeMapping> mappings = new ArrayList<AbstractCodeMapping>(extraMapper.getMappings());
					if(mappings.size() == 1) {
						Set<Replacement> replacements = mappings.get(0).getReplacements();
						if(replacements.size() == 1) {
							Replacement replacement = replacements.iterator().next();
							List<String> parameterNames1 = operation1.getParameterNameList();
							List<String> parameterNames2 = operation2.getParameterNameList();
							for(int i=0; i<parameterNames1.size(); i++) {
								String parameterName1 = parameterNames1.get(i);
								String parameterName2 = parameterNames2.get(i);
								if(replacement.getBefore().equals(parameterName1) &&
										replacement.getAfter().equals(parameterName2)) {
									firstMappers.add(extraMapper);
									break;
								}
							}
						}
					}
				}
			}
		}
		return firstMappers;
	}

	private boolean allRenamedOperations(List<UMLOperationBodyMapper> mappers) {
		for (UMLOperationBodyMapper mapper : mappers) {
			if(mapper.getOperation1().getName().equals(mapper.getOperation2().getName())) {
				return false;
			}
		}
		return true;
	}

   private boolean sameSourceAndTargetClass(List<UMLOperationBodyMapper> mappers) {
	   if(mappers.size() == 1) {
		   return false;
	   }
	   String sourceClassName = null;
	   String targetClassName = null;
	   for (UMLOperationBodyMapper mapper : mappers) {
		   String mapperSourceClassName = mapper.getOperation1().getClassName();
		   if(sourceClassName == null) {
			   sourceClassName = mapperSourceClassName;
		   }
		   else if(!mapperSourceClassName.equals(sourceClassName)) {
			   return false;
		   }
		   String mapperTargetClassName = mapper.getOperation2().getClassName();
		   if(targetClassName == null) {
			   targetClassName = mapperTargetClassName;
		   }
		   else if(!mapperTargetClassName.equals(targetClassName)) {
			   return false;
		   }
	   }
	   return true;
   }

   private boolean mappedElementsMoreThanNonMappedT1AndT2(int mappings, UMLOperationBodyMapper operationBodyMapper) {
        int nonMappedElementsT1 = operationBodyMapper.nonMappedElementsT1();
		int nonMappedElementsT2 = operationBodyMapper.nonMappedElementsT2();
		UMLClass addedClass = getAddedClass(operationBodyMapper.getOperation2().getClassName());
		int nonMappedStatementsDeclaringSameVariable = 0;
		for(ListIterator<StatementObject> leafIterator1 = operationBodyMapper.getNonMappedLeavesT1().listIterator(); leafIterator1.hasNext();) {
			StatementObject s1 = leafIterator1.next();
			for(StatementObject s2 : operationBodyMapper.getNonMappedLeavesT2()) {
				if(s1.getVariableDeclarations().size() == 1 && s2.getVariableDeclarations().size() == 1) {
					VariableDeclaration v1 = s1.getVariableDeclarations().get(0);
					VariableDeclaration v2 = s2.getVariableDeclarations().get(0);
					if(v1.getVariableName().equals(v2.getVariableName()) && v1.getType().equals(v2.getType())) {
						nonMappedStatementsDeclaringSameVariable++;
					}
				}
			}
			if(addedClass != null && s1.getVariableDeclarations().size() == 1) {
				VariableDeclaration v1 = s1.getVariableDeclarations().get(0);
				for(UMLAttribute attribute : addedClass.getAttributes()) {
					VariableDeclaration attributeDeclaration = attribute.getVariableDeclaration();
					if(attributeDeclaration.getInitializer() != null && v1.getInitializer() != null) {
						String attributeInitializer = attributeDeclaration.getInitializer().getString();
						String variableInitializer = v1.getInitializer().getString();
						if(attributeInitializer.equals(variableInitializer) && attribute.getType().equals(v1.getType()) &&
								(attribute.getName().equals(v1.getVariableName()) ||
								attribute.getName().toLowerCase().contains(v1.getVariableName().toLowerCase()) ||
								v1.getVariableName().toLowerCase().contains(attribute.getName().toLowerCase()))) {
							nonMappedStatementsDeclaringSameVariable++;
							leafIterator1.remove();
							LeafMapping mapping = new LeafMapping(v1.getInitializer(), attributeDeclaration.getInitializer(), operationBodyMapper.getOperation1(), operationBodyMapper.getOperation2());
							operationBodyMapper.getMappings().add(mapping);
							break;
						}
					}
				}
			}
		}
		int nonMappedLoopsIteratingOverSameVariable = 0;
		for(CompositeStatementObject c1 : operationBodyMapper.getNonMappedInnerNodesT1()) {
			if(c1.isLoop()) {
				for(CompositeStatementObject c2 : operationBodyMapper.getNonMappedInnerNodesT2()) {
					if(c2.isLoop()) {
						Set<String> intersection = new LinkedHashSet<String>(c1.getVariables());
						intersection.retainAll(c2.getVariables());
						if(!intersection.isEmpty()) {
							nonMappedLoopsIteratingOverSameVariable++;
						}
					}
				}
			}
		}
		return (mappings > nonMappedElementsT1-nonMappedStatementsDeclaringSameVariable-nonMappedLoopsIteratingOverSameVariable &&
				mappings > nonMappedElementsT2-nonMappedStatementsDeclaringSameVariable-nonMappedLoopsIteratingOverSameVariable) ||
				(nonMappedElementsT1-nonMappedStatementsDeclaringSameVariable-nonMappedLoopsIteratingOverSameVariable == 0 && mappings > Math.floor(nonMappedElementsT2/2.0)) ||
				(nonMappedElementsT2-nonMappedStatementsDeclaringSameVariable-nonMappedLoopsIteratingOverSameVariable == 0 && mappings > Math.floor(nonMappedElementsT1/2.0));
   }

   private boolean movedAndRenamedMethodSignature(UMLOperation removedOperation, UMLOperation addedOperation, UMLOperationBodyMapper mapper) {
	   UMLClassBaseDiff removedOperationClassDiff = getUMLClassDiff(removedOperation.getClassName());
	   if(removedOperationClassDiff != null && removedOperationClassDiff.containsOperationWithTheSameSignatureInNextClass(removedOperation)) {
		   return false;
	   }
	   if((removedOperation.isGetter() || removedOperation.isSetter() || addedOperation.isGetter() || addedOperation.isSetter()) &&
			   mapper.mappingsWithoutBlocks() == 1 && mapper.getMappings().size() == 1) {
		   if(!mapper.getMappings().iterator().next().isExact()) {
			   return false;
		   }
	   }
	   if((removedOperation.isConstructor() || addedOperation.isConstructor()) && mapper.mappingsWithoutBlocks() > 0) {
		   if(!(UMLClassBaseDiff.allMappingsAreExactMatches(mapper) && mapper.nonMappedElementsT1() == 0 && mapper.nonMappedElementsT2() == 0)) {
			   return false;
		   }
	   }
	   int exactLeafMappings = 0;
	   for(AbstractCodeMapping mapping : mapper.getMappings()) {
		   if(mapping instanceof LeafMapping && mapping.isExact() && !mapping.getFragment1().getString().startsWith("return ")) {
			   exactLeafMappings++;
		   }
	   }
	   double normalizedEditDistance = mapper.normalizedEditDistance();
	   if(exactLeafMappings == 0 && normalizedEditDistance > 0.24) {
		   return false;
	   }
	   if(exactLeafMappings == 1 && normalizedEditDistance > 0.5 && (mapper.nonMappedElementsT1() > 0 || mapper.nonMappedElementsT2() > 0)) {
		   return false;
	   }
	   if(mapper.mappingsWithoutBlocks() == 1) {
		   for(AbstractCodeMapping mapping : mapper.getMappings()) {
			   String fragment1 = mapping.getFragment1().getString();
			   String fragment2 = mapping.getFragment2().getString();
			   if(fragment1.startsWith("return true;") || fragment1.startsWith("return false;") || fragment1.startsWith("return this;") || fragment1.startsWith("return null;") || fragment1.startsWith("return;") ||
					   fragment2.startsWith("return true;") || fragment2.startsWith("return false;") || fragment2.startsWith("return this;") || fragment2.startsWith("return null;") || fragment2.startsWith("return;")) {
				   return false;
			   }
		   }
	   }
	   if(addedOperation.isAbstract() == removedOperation.isAbstract() &&
			   addedOperation.getTypeParameters().equals(removedOperation.getTypeParameters())) {
		   List<UMLType> addedOperationParameterTypeList = addedOperation.getParameterTypeList();
		   List<UMLType> removedOperationParameterTypeList = removedOperation.getParameterTypeList();
		   if(addedOperationParameterTypeList.equals(removedOperationParameterTypeList) && addedOperationParameterTypeList.size() > 0) {
			   return true;
		   }
		   else {
			   // ignore parameters of types sourceClass and targetClass
			   List<UMLParameter> oldParameters = new ArrayList<UMLParameter>();
			   Set<String> oldParameterNames = new LinkedHashSet<String>();
			   for (UMLParameter oldParameter : removedOperation.getParameters()) {
				   if (!oldParameter.getKind().equals("return")
						   && !looksLikeSameType(oldParameter.getType().getClassType(), addedOperation.getClassName())
						   && !looksLikeSameType(oldParameter.getType().getClassType(), removedOperation.getClassName())) {
					   oldParameters.add(oldParameter);
					   oldParameterNames.add(oldParameter.getName());
				   }
			   }
			   List<UMLParameter> newParameters = new ArrayList<UMLParameter>();
			   Set<String> newParameterNames = new LinkedHashSet<String>();
			   for (UMLParameter newParameter : addedOperation.getParameters()) {
				   if (!newParameter.getKind().equals("return") &&
						   !looksLikeSameType(newParameter.getType().getClassType(), addedOperation.getClassName()) &&
						   !looksLikeSameType(newParameter.getType().getClassType(), removedOperation.getClassName())) {
					   newParameters.add(newParameter);
					   newParameterNames.add(newParameter.getName());
				   }
			   }
			   Set<String> intersection = new LinkedHashSet<String>(oldParameterNames);
			   intersection.retainAll(newParameterNames);
			   boolean parameterMatch = oldParameters.equals(newParameters) || oldParameters.containsAll(newParameters) || newParameters.containsAll(oldParameters) || intersection.size() > 0 ||
					   removedOperation.isStatic() || addedOperation.isStatic();
			   return (parameterMatch && oldParameters.size() > 0 && newParameters.size() > 0) ||
					   (parameterMatch && addedOperation.equalReturnParameter(removedOperation) && (oldParameters.size() == 0 || newParameters.size() == 0));
		   }
	   }
	   return false;
   }

   private boolean movedMethodSignature(UMLOperation removedOperation, UMLOperation addedOperation) {
	   if(addedOperation.getName().equals(removedOperation.getName()) &&
			   addedOperation.equalReturnParameter(removedOperation) &&
			   addedOperation.isAbstract() == removedOperation.isAbstract() &&
			   addedOperation.getTypeParameters().equals(removedOperation.getTypeParameters())) {
		   if(addedOperation.getParameters().equals(removedOperation.getParameters())) {
			   return true;
		   }
		   else {
			   // ignore parameters of types sourceClass and targetClass
			   List<UMLParameter> oldParameters = new ArrayList<UMLParameter>();
			   Set<String> oldParameterNames = new LinkedHashSet<String>();
			   for (UMLParameter oldParameter : removedOperation.getParameters()) {
				   if (!oldParameter.getKind().equals("return")
						   && !looksLikeSameType(oldParameter.getType().getClassType(), addedOperation.getClassName())
						   && !looksLikeSameType(oldParameter.getType().getClassType(), removedOperation.getClassName())) {
					   oldParameters.add(oldParameter);
					   oldParameterNames.add(oldParameter.getName());
				   }
			   }
			   List<UMLParameter> newParameters = new ArrayList<UMLParameter>();
			   Set<String> newParameterNames = new LinkedHashSet<String>();
			   for (UMLParameter newParameter : addedOperation.getParameters()) {
				   if (!newParameter.getKind().equals("return") &&
						   !looksLikeSameType(newParameter.getType().getClassType(), addedOperation.getClassName()) &&
						   !looksLikeSameType(newParameter.getType().getClassType(), removedOperation.getClassName())) {
					   newParameters.add(newParameter);
					   newParameterNames.add(newParameter.getName());
				   }
			   }
			   Set<String> intersection = new LinkedHashSet<String>(oldParameterNames);
			   intersection.retainAll(newParameterNames);
			   return oldParameters.equals(newParameters) || oldParameters.containsAll(newParameters) || newParameters.containsAll(oldParameters) || intersection.size() > 0 ||
					   removedOperation.isStatic() || addedOperation.isStatic();
		   }
	   }
	   return false;
   }

   private boolean refactoringListContainsAnotherMoveRefactoringWithTheSameOperations(UMLOperation removedOperation, UMLOperation addedOperation) {
	   for(Refactoring refactoring : refactorings) {
		   if(refactoring instanceof MoveOperationRefactoring) {
			   MoveOperationRefactoring moveRefactoring = (MoveOperationRefactoring)refactoring;
			   if(moveRefactoring.getOriginalOperation().equals(removedOperation)) {
				   return true;
			   }
		   }
	   }
	   return false;
   }

	private boolean attributeMerged(UMLAttribute a1, UMLAttribute a2, Set<Refactoring> refactorings) {
		for(Refactoring refactoring : refactorings) {
			if(refactoring instanceof MergeAttributeRefactoring) {
				MergeAttributeRefactoring merge = (MergeAttributeRefactoring)refactoring;
				if(merge.getMergedVariables().contains(a1.getVariableDeclaration()) && merge.getNewAttribute().getVariableDeclaration().equals(a2.getVariableDeclaration())) {
					return true;
				}
			}
		}
		return false;
	}

	private Refactoring attributeRenamed(Set<VariableDeclaration> mergedAttributes, VariableDeclaration a2, Set<Refactoring> refactorings) {
		for(Refactoring refactoring : refactorings) {
			if(refactoring instanceof RenameAttributeRefactoring) {
				RenameAttributeRefactoring rename = (RenameAttributeRefactoring)refactoring;
				if(mergedAttributes.contains(rename.getOriginalAttribute().getVariableDeclaration()) && a2.equals(rename.getRenamedAttribute().getVariableDeclaration())) {
					return rename;
				}
			}
		}
		return null;
	}

   private void deleteRemovedOperation(UMLOperation operation) {
      UMLClassBaseDiff classDiff = getUMLClassDiff(operation.getClassName());
      if(classDiff != null)
    	  classDiff.getRemovedOperations().remove(operation);
   }
   
   private void deleteAddedOperation(UMLOperation operation) {
      UMLClassBaseDiff classDiff = getUMLClassDiff(operation.getClassName());
      if(classDiff != null)
    	  classDiff.getAddedOperations().remove(operation);
   }

	private static boolean isNumeric(String str) {
		for(char c : str.toCharArray()) {
			if(!Character.isDigit(c)) return false;
		}
		return true;
	}
}
